elements-kit 0.0.1 → 0.0.3
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 +325 -15
- package/dist/attributes.d.mts +90 -0
- package/dist/attributes.mjs +81 -0
- package/dist/builder/dom.d.mts +212 -0
- package/dist/builder/dom.mjs +205 -0
- package/dist/builder/index.d.mts +2 -0
- package/dist/builder/index.mjs +113 -0
- package/dist/element-CCHXkEsj.mjs +262 -0
- package/dist/index-C6xwOPCO.d.mts +263 -0
- package/dist/index-DJejH8Ff.d.mts +89 -0
- package/dist/index.d.mts +36 -0
- package/dist/index.mjs +127 -0
- package/dist/jsx-runtime/index.d.mts +70 -0
- package/dist/jsx-runtime/index.mjs +2 -0
- package/dist/lib-B2drrxlV.mjs +15 -0
- package/dist/polyfill-BEL-HWkO.d.mts +14 -0
- package/dist/signals/index.d.mts +2 -0
- package/dist/signals/index.mjs +2 -0
- package/dist/signals/lib/media.d.mts +14 -0
- package/dist/signals/lib/media.mjs +26 -0
- package/dist/signals/lib/react.d.mts +62 -0
- package/dist/signals/lib/react.mjs +82 -0
- package/dist/signals-Cr7xgAJH.mjs +966 -0
- package/dist/slot.d.mts +73 -0
- package/dist/slot.mjs +142 -0
- package/package.json +69 -15
- package/dist/builder.d.mts +0 -11799
- package/dist/builder.mjs +0 -208
- package/dist/chunk-CFhWmker.mjs +0 -36
- package/dist/core.d.mts +0 -14
- package/dist/core.mjs +0 -74
- package/dist/signals-BGUPt0zl.mjs +0 -13
- package/dist/signals-BzhB4Ch2.d.mts +0 -10
- package/dist/signals.d.mts +0 -3
- package/dist/signals.mjs +0 -5
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { n as disposeElement } from "./element-CCHXkEsj.mjs";
|
|
2
|
+
import { a as effect, f as signal, m as untracked } from "./signals-Cr7xgAJH.mjs";
|
|
3
|
+
//#region src/components/for.ts
|
|
4
|
+
/**
|
|
5
|
+
* Keyed list renderer. Reconciles a reactive array into the DOM using a key
|
|
6
|
+
* function to match existing nodes — minimising create/destroy churn.
|
|
7
|
+
*
|
|
8
|
+
* Reconciliation strategy (inspired by udomdiff / dom-expressions):
|
|
9
|
+
* 1. Remove stale entries (keys absent from the new array).
|
|
10
|
+
* 2. Skip unchanged common prefix and suffix.
|
|
11
|
+
* 3. Pure-append fast path when the surviving old range is empty.
|
|
12
|
+
* 4. Backward scan of the middle region — move or create entries so that
|
|
13
|
+
* each entry's range lands immediately before the already-correct cursor.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <For each={visibleTodos} by={(todo) => todo.id}>
|
|
18
|
+
* {(todo) => <li>{todo.text}</li>}
|
|
19
|
+
* </For>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Props:
|
|
23
|
+
* each — reactive getter returning the array (e.g. a `computed`)
|
|
24
|
+
* by — extracts a stable key per item (default: index)
|
|
25
|
+
* children — render function called once per new key
|
|
26
|
+
*/
|
|
27
|
+
var For = class {
|
|
28
|
+
#each = signal([]);
|
|
29
|
+
get each() {
|
|
30
|
+
return this.#each();
|
|
31
|
+
}
|
|
32
|
+
set each(v) {
|
|
33
|
+
this.#each(v);
|
|
34
|
+
}
|
|
35
|
+
by = (_, i) => i;
|
|
36
|
+
children = () => null;
|
|
37
|
+
#start = document.createComment("<For>");
|
|
38
|
+
#end = document.createComment("</For>");
|
|
39
|
+
#cache = /* @__PURE__ */ new Map();
|
|
40
|
+
/** Keys in current DOM order — needed for prefix/suffix optimisation. */
|
|
41
|
+
#order = [];
|
|
42
|
+
render() {
|
|
43
|
+
const fragment = document.createDocumentFragment();
|
|
44
|
+
fragment.appendChild(this.#start);
|
|
45
|
+
fragment.appendChild(this.#end);
|
|
46
|
+
effect(() => this.#reconcile());
|
|
47
|
+
return fragment;
|
|
48
|
+
}
|
|
49
|
+
#reconcile() {
|
|
50
|
+
const parent = this.#start.parentNode;
|
|
51
|
+
if (!parent) return;
|
|
52
|
+
const items = this.each;
|
|
53
|
+
const b = items.map((item, i) => this.by(item, i));
|
|
54
|
+
const bSet = new Set(b);
|
|
55
|
+
for (const [key, entry] of this.#cache) if (!bSet.has(key)) {
|
|
56
|
+
removeEntry(entry);
|
|
57
|
+
this.#cache.delete(key);
|
|
58
|
+
}
|
|
59
|
+
const a = this.#order.filter((k) => this.#cache.has(k));
|
|
60
|
+
let aStart = 0, aEnd = a.length;
|
|
61
|
+
let bStart = 0, bEnd = b.length;
|
|
62
|
+
while (aStart < aEnd && bStart < bEnd && a[aStart] === b[bStart]) {
|
|
63
|
+
aStart++;
|
|
64
|
+
bStart++;
|
|
65
|
+
}
|
|
66
|
+
while (aEnd > aStart && bEnd > bStart && a[aEnd - 1] === b[bEnd - 1]) {
|
|
67
|
+
aEnd--;
|
|
68
|
+
bEnd--;
|
|
69
|
+
}
|
|
70
|
+
const after = bEnd < b.length ? this.#cache.get(b[bEnd]).start : this.#end;
|
|
71
|
+
if (aStart === aEnd) {
|
|
72
|
+
let cursor = after;
|
|
73
|
+
for (let i = bEnd - 1; i >= bStart; i--) {
|
|
74
|
+
const key = b[i];
|
|
75
|
+
const entry = this.#makeEntry(key);
|
|
76
|
+
insertEntry(parent, entry, cursor, untracked(() => this.children(items[i], i)));
|
|
77
|
+
this.#cache.set(key, entry);
|
|
78
|
+
cursor = entry.start;
|
|
79
|
+
}
|
|
80
|
+
this.#order = [...b];
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
let cursor = after;
|
|
84
|
+
for (let i = bEnd - 1; i >= bStart; i--) {
|
|
85
|
+
const key = b[i];
|
|
86
|
+
let entry = this.#cache.get(key);
|
|
87
|
+
if (!entry) {
|
|
88
|
+
entry = this.#makeEntry(key);
|
|
89
|
+
insertEntry(parent, entry, cursor, untracked(() => this.children(items[i], i)));
|
|
90
|
+
this.#cache.set(key, entry);
|
|
91
|
+
} else if (entry.end.nextSibling !== cursor) moveEntry(parent, entry, cursor);
|
|
92
|
+
cursor = entry.start;
|
|
93
|
+
}
|
|
94
|
+
this.#order = [...b];
|
|
95
|
+
}
|
|
96
|
+
#makeEntry(key) {
|
|
97
|
+
return {
|
|
98
|
+
start: document.createComment(`[${key}]`),
|
|
99
|
+
end: document.createComment(`[/${key}]`)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
function insertEntry(parent, entry, before, rendered) {
|
|
104
|
+
parent.insertBefore(entry.start, before);
|
|
105
|
+
if (rendered) parent.insertBefore(rendered, before);
|
|
106
|
+
parent.insertBefore(entry.end, before);
|
|
107
|
+
}
|
|
108
|
+
function removeEntry(entry) {
|
|
109
|
+
let node = entry.start.nextSibling;
|
|
110
|
+
while (node && node !== entry.end) {
|
|
111
|
+
const next = node.nextSibling;
|
|
112
|
+
if (node instanceof Element) disposeElement(node);
|
|
113
|
+
node = next;
|
|
114
|
+
}
|
|
115
|
+
const range = document.createRange();
|
|
116
|
+
range.setStartBefore(entry.start);
|
|
117
|
+
range.setEndAfter(entry.end);
|
|
118
|
+
range.deleteContents();
|
|
119
|
+
}
|
|
120
|
+
function moveEntry(parent, entry, before) {
|
|
121
|
+
const range = document.createRange();
|
|
122
|
+
range.setStartBefore(entry.start);
|
|
123
|
+
range.setEndAfter(entry.end);
|
|
124
|
+
parent.insertBefore(range.extractContents(), before);
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { For };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { t as PrimitiveNodeType } from "../polyfill-BEL-HWkO.mjs";
|
|
2
|
+
import { JSX as JSX$1 } from "dom-expressions/src/jsx-h";
|
|
3
|
+
|
|
4
|
+
//#region src/jsx-runtime/types.d.ts
|
|
5
|
+
/** An instance created by a component class — must expose `render()`. */
|
|
6
|
+
interface ComponentInstance {
|
|
7
|
+
render(): Element | DocumentFragment | null;
|
|
8
|
+
}
|
|
9
|
+
/** A class whose constructor returns a ComponentInstance. */
|
|
10
|
+
type ComponentClass = new (...args: any[]) => ComponentInstance;
|
|
11
|
+
/** Anything that can appear as a JSX child. */
|
|
12
|
+
type Child = PrimitiveNodeType | AnyFn | Child[];
|
|
13
|
+
/** A function that renders props into an element or fragment. */
|
|
14
|
+
type AnyFn = (...args: any[]) => any;
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/jsx-runtime/ref.d.ts
|
|
17
|
+
declare const $ref: unique symbol;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/jsx-runtime/element.d.ts
|
|
20
|
+
declare function createElement(type: string | Element | DocumentFragment | ComponentClass | ((props: Record<string | symbol, unknown>) => null | Element | DocumentFragment), {
|
|
21
|
+
[$ref]: ref,
|
|
22
|
+
...props
|
|
23
|
+
}?: Record<string | symbol, unknown>): Element | DocumentFragment | null;
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/jsx-runtime/index.d.ts
|
|
26
|
+
/** A value or a reactive zero-argument getter. */
|
|
27
|
+
type FunctionMaybe<T> = JSX$1.FunctionMaybe<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Maps slot names to `Child` content.
|
|
30
|
+
* Use this to type `slot:name` JSX props on a custom component.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* function Card(props: { title: string } & SlotProps<"header" | "footer">) { … }
|
|
35
|
+
* // caller: <Card title="…" slot:header={<h1>…</h1>} slot:footer={<p>…</p>} />
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
type SlotProps<K extends string> = { [P in K as `slot:${P}`]?: Child };
|
|
39
|
+
/**
|
|
40
|
+
* Get the full JSX prop types for a given tag name, including reactive
|
|
41
|
+
* attributes, events, and all our namespace extensions.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* type InputProps = Attrs<"input">; // typed props for <input>
|
|
46
|
+
* type DivProps = Attrs<"div">; // typed props for <div>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
type Attrs<K extends keyof JSX.IntrinsicElements> = JSX.IntrinsicElements[K];
|
|
50
|
+
/** Extra props injected into every intrinsic element beyond dom-expressions defaults. */
|
|
51
|
+
type OurProps = {
|
|
52
|
+
[$ref]?: (el: Element) => void;
|
|
53
|
+
[slot: `slot:${string}`]: Child;
|
|
54
|
+
[cls: `class:${string}`]: FunctionMaybe<boolean>;
|
|
55
|
+
[sty: `style:${string}`]: FunctionMaybe<string | null>;
|
|
56
|
+
[prop: `prop:${string}`]: unknown;
|
|
57
|
+
};
|
|
58
|
+
type WithOurProps<T> = T & OurProps;
|
|
59
|
+
declare namespace JSX {
|
|
60
|
+
type Element = globalThis.Element | globalThis.DocumentFragment | null;
|
|
61
|
+
type ElementType = Child | ComponentClass;
|
|
62
|
+
interface ElementChildrenAttribute {
|
|
63
|
+
children: {};
|
|
64
|
+
}
|
|
65
|
+
type IntrinsicElements = { [K in keyof JSX$1.IntrinsicElements]: WithOurProps<JSX$1.IntrinsicElements[K]> } & {
|
|
66
|
+
/** Custom elements (`x-foo`, `my-component`, …) get a loose typed fallback. */[customElement: `${string}-${string}`]: WithOurProps<JSX$1.DOMAttributes<HTMLElement>> & Record<string, unknown>;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { Attrs, FunctionMaybe, JSX, SlotProps, createElement as h, createElement as jsx, createElement as jsxDEV, createElement as jsxs };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/lib.ts
|
|
2
|
+
var UnsupportedChildError = class extends Error {
|
|
3
|
+
constructor(value) {
|
|
4
|
+
super(`Unsupported child type: ${typeof value}`);
|
|
5
|
+
this.name = "UnsupportedChildError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
function resolveNode(c) {
|
|
9
|
+
if (c instanceof Node) return c;
|
|
10
|
+
if (c === null || c === void 0 || typeof c === "boolean") return document.createComment("");
|
|
11
|
+
if (typeof c === "string" || typeof c === "number" || typeof c === "bigint" || typeof c === "symbol" || c instanceof Date || c instanceof RegExp) return document.createTextNode(String(c));
|
|
12
|
+
throw new UnsupportedChildError(c);
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { resolveNode as t };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/lib.d.ts
|
|
2
|
+
type PrimitiveNodeType = Node | string | boolean | number | bigint | symbol | Date | RegExp | null | undefined;
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region src/polyfill.d.ts
|
|
5
|
+
declare global {
|
|
6
|
+
interface SymbolConstructor {
|
|
7
|
+
readonly dispose: symbol;
|
|
8
|
+
}
|
|
9
|
+
interface Disposable {
|
|
10
|
+
[Symbol.dispose](): void;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { PrimitiveNodeType as t };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as untracked, a as reactive, c as effect, d as isEffect, f as isEffectScope, g as trigger, h as signal, i as isReactive, l as effectScope, m as onCleanup, n as Signal, o as batch, p as isSignal, r as Updater, s as computed, t as Computed, u as isComputed } from "../index-C6xwOPCO.mjs";
|
|
2
|
+
export { Computed, Signal, Updater, batch, computed, effect, effectScope, isComputed, isEffect, isEffectScope, isReactive, isSignal, onCleanup, reactive, signal, trigger, untracked };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as effect, c as isEffect, d as onCleanup, f as signal, i as computed, l as isEffectScope, m as untracked, n as reactive, o as effectScope, p as trigger, r as batch, s as isComputed, t as isReactive, u as isSignal } from "../signals-Cr7xgAJH.mjs";
|
|
2
|
+
export { batch, computed, effect, effectScope, isComputed, isEffect, isEffectScope, isReactive, isSignal, onCleanup, reactive, signal, trigger, untracked };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { t as Computed } from "../../index-C6xwOPCO.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/signals/lib/media.d.ts
|
|
4
|
+
declare const isBrowser: boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Creates a signal that tracks a CSS media query.
|
|
7
|
+
*
|
|
8
|
+
* @param query The media query string (e.g. '(max-width: 600px)')
|
|
9
|
+
* @param defaultState The default value (for SSR/hydration)
|
|
10
|
+
* @returns Computed<boolean> that is true if the query matches
|
|
11
|
+
*/
|
|
12
|
+
declare function createMediaSignal(query: string, defaultState?: boolean): Computed<boolean>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { createMediaSignal, isBrowser };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { d as onCleanup, f as signal } from "../../signals-Cr7xgAJH.mjs";
|
|
2
|
+
//#region src/signals/lib/media.ts
|
|
3
|
+
const isBrowser = typeof window !== "undefined";
|
|
4
|
+
/**
|
|
5
|
+
* Creates a signal that tracks a CSS media query.
|
|
6
|
+
*
|
|
7
|
+
* @param query The media query string (e.g. '(max-width: 600px)')
|
|
8
|
+
* @param defaultState The default value (for SSR/hydration)
|
|
9
|
+
* @returns Computed<boolean> that is true if the query matches
|
|
10
|
+
*/
|
|
11
|
+
function createMediaSignal(query, defaultState) {
|
|
12
|
+
if (!isBrowser) return signal(defaultState ?? false);
|
|
13
|
+
const mql = window.matchMedia(query);
|
|
14
|
+
const state = signal(mql.matches);
|
|
15
|
+
const handler = () => {
|
|
16
|
+
state(mql.matches);
|
|
17
|
+
};
|
|
18
|
+
mql.addEventListener("change", handler);
|
|
19
|
+
const cleanup = () => {
|
|
20
|
+
mql.removeEventListener("change", handler);
|
|
21
|
+
};
|
|
22
|
+
onCleanup(cleanup);
|
|
23
|
+
return Object.assign(state, { [Symbol.dispose]: cleanup });
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { createMediaSignal, isBrowser };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { t as Computed } from "../../index-C6xwOPCO.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/signals/lib/react.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Subscribe to any readable signal — writable or computed — returning its current value.
|
|
6
|
+
*
|
|
7
|
+
* Accepts any zero-argument callable `() => T`, which includes both `Signal<T>` and
|
|
8
|
+
* `Computed<T>`. Using `() => T` instead of `Computed<T>` prevents TypeScript from
|
|
9
|
+
* picking the write overload of `Signal<T>` during type inference.
|
|
10
|
+
*
|
|
11
|
+
* @template T - The type of the signal value.
|
|
12
|
+
* @param value - A writable `Signal<T>` or a derived `Computed<T>`.
|
|
13
|
+
* @returns The current value, updated on every signal change.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* const count = signal(0);
|
|
18
|
+
* const double = computed(() => count() * 2);
|
|
19
|
+
*
|
|
20
|
+
* function Display() {
|
|
21
|
+
* const countValue = useSignal(count);
|
|
22
|
+
* const doubleValue = useSignal(double);
|
|
23
|
+
* return <div>{countValue} × 2 = {doubleValue}</div>;
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare function useSignal<T>(value: () => T): T;
|
|
28
|
+
/**
|
|
29
|
+
* Create a signal effect scope tied to a React component's lifetime.
|
|
30
|
+
*
|
|
31
|
+
* All effects registered inside `callback` are grouped into a single scope. The scope — and every effect within it — is automatically stopped when the component unmounts.
|
|
32
|
+
*
|
|
33
|
+
* If your callback returns a `Computed<T>` signal, the hook will always return its current value, updating reactively as dependencies change. If your callback returns `void`, the value will be `undefined`.
|
|
34
|
+
*
|
|
35
|
+
* Returns the current value of the computed signal (or `undefined`).
|
|
36
|
+
*
|
|
37
|
+
* Use this when you want to create multiple related effects at once without individually managing each one's lifecycle. All effects and cleanups inside the callback are automatically cleaned up on unmount.
|
|
38
|
+
*
|
|
39
|
+
* @template T - The type of the computed value (if any).
|
|
40
|
+
* @param callback - A function that registers one or more signal effects, optionally returning a `Computed<T>`.
|
|
41
|
+
* @returns `value` — the current value of the computed signal (or `undefined`).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* function Analytics() {
|
|
46
|
+
* useScope(() => {
|
|
47
|
+
* effect(() => console.log("page:", currentPage()));
|
|
48
|
+
* effect(() => console.log("user:", currentUser()));
|
|
49
|
+
* });
|
|
50
|
+
* return null;
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* // With computed value:
|
|
54
|
+
* function DoubleCounter() {
|
|
55
|
+
* const double = useScope(() => computed(() => count() * 2));
|
|
56
|
+
* return <div>{double}</div>;
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function useScope<T>(callback: () => Computed<T> | void): T | void;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { useScope, useSignal };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { a as effect, f as signal, o as effectScope } from "../../signals-Cr7xgAJH.mjs";
|
|
2
|
+
import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
//#region src/signals/lib/react.ts
|
|
4
|
+
/**
|
|
5
|
+
* Subscribe to any readable signal — writable or computed — returning its current value.
|
|
6
|
+
*
|
|
7
|
+
* Accepts any zero-argument callable `() => T`, which includes both `Signal<T>` and
|
|
8
|
+
* `Computed<T>`. Using `() => T` instead of `Computed<T>` prevents TypeScript from
|
|
9
|
+
* picking the write overload of `Signal<T>` during type inference.
|
|
10
|
+
*
|
|
11
|
+
* @template T - The type of the signal value.
|
|
12
|
+
* @param value - A writable `Signal<T>` or a derived `Computed<T>`.
|
|
13
|
+
* @returns The current value, updated on every signal change.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* const count = signal(0);
|
|
18
|
+
* const double = computed(() => count() * 2);
|
|
19
|
+
*
|
|
20
|
+
* function Display() {
|
|
21
|
+
* const countValue = useSignal(count);
|
|
22
|
+
* const doubleValue = useSignal(double);
|
|
23
|
+
* return <div>{countValue} × 2 = {doubleValue}</div>;
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
function useSignal(value) {
|
|
28
|
+
return useSyncExternalStore((callback) => effect(() => {
|
|
29
|
+
value();
|
|
30
|
+
callback();
|
|
31
|
+
}), () => value(), () => value());
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a signal effect scope tied to a React component's lifetime.
|
|
35
|
+
*
|
|
36
|
+
* All effects registered inside `callback` are grouped into a single scope. The scope — and every effect within it — is automatically stopped when the component unmounts.
|
|
37
|
+
*
|
|
38
|
+
* If your callback returns a `Computed<T>` signal, the hook will always return its current value, updating reactively as dependencies change. If your callback returns `void`, the value will be `undefined`.
|
|
39
|
+
*
|
|
40
|
+
* Returns the current value of the computed signal (or `undefined`).
|
|
41
|
+
*
|
|
42
|
+
* Use this when you want to create multiple related effects at once without individually managing each one's lifecycle. All effects and cleanups inside the callback are automatically cleaned up on unmount.
|
|
43
|
+
*
|
|
44
|
+
* @template T - The type of the computed value (if any).
|
|
45
|
+
* @param callback - A function that registers one or more signal effects, optionally returning a `Computed<T>`.
|
|
46
|
+
* @returns `value` — the current value of the computed signal (or `undefined`).
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* function Analytics() {
|
|
51
|
+
* useScope(() => {
|
|
52
|
+
* effect(() => console.log("page:", currentPage()));
|
|
53
|
+
* effect(() => console.log("user:", currentUser()));
|
|
54
|
+
* });
|
|
55
|
+
* return null;
|
|
56
|
+
* }
|
|
57
|
+
*
|
|
58
|
+
* // With computed value:
|
|
59
|
+
* function DoubleCounter() {
|
|
60
|
+
* const double = useScope(() => computed(() => count() * 2));
|
|
61
|
+
* return <div>{double}</div>;
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
function useScope(callback) {
|
|
66
|
+
const computedRef = useRef(void 0);
|
|
67
|
+
const stopScope = useMemo(() => {
|
|
68
|
+
return effectScope(() => {
|
|
69
|
+
computedRef.current = callback();
|
|
70
|
+
});
|
|
71
|
+
}, [callback]);
|
|
72
|
+
const value = useSignal(computedRef.current ?? fallbackSignal);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
return () => {
|
|
75
|
+
stopScope();
|
|
76
|
+
};
|
|
77
|
+
}, [stopScope]);
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
const fallbackSignal = signal(void 0);
|
|
81
|
+
//#endregion
|
|
82
|
+
export { useScope, useSignal };
|