Skip to content

Commit d0ed23b

Browse files
feat(.changeset/*.md) context support in vanjs package added to changeset (#16)
1 parent 29c83b7 commit d0ed23b

4 files changed

Lines changed: 140 additions & 60 deletions

File tree

.changeset/eager-lies-behave.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
"@michthemaker/vanjs": minor
3+
---
4+
5+
Add Context API for logical state sharing across component trees
6+
7+
Introduces `createContext` and `useContext` functions that enable sharing reactive state without prop drilling. Context uses logical scoping (not DOM-based) and integrates seamlessly with VanJS's reactive state system.
8+
9+
**New exports:**
10+
11+
- `createContext<T>()`: Creates a new context object with a Provider method
12+
- `useContext<T>(context)`: Retrieves the current context value (must be called within a Provider)
13+
14+
**Example usage:**
15+
16+
```typescript
17+
import van, { createContext, useContext } from "@michthemaker/vanjs";
18+
19+
const { div, button } = van.tags;
20+
const ThemeContext = createContext<{ color: string }>();
21+
22+
const theme = van.state({ color: "blue" });
23+
24+
ThemeContext.Provider(theme, () => {
25+
const currentTheme = useContext(ThemeContext);
26+
return div(
27+
() => `Theme: ${currentTheme.val.color}`,
28+
button(
29+
{
30+
onclick: () => (theme.val = { color: "red" }),
31+
},
32+
"Change Theme"
33+
)
34+
);
35+
});
36+
```
37+
38+
**Features:**
39+
40+
- Shallow reactivity: context values must be VanJS state objects
41+
- Supports nested providers of the same context
42+
- Type-safe with TypeScript generics
43+
- Throws helpful errors when used incorrectly

.changeset/funky-humans-open.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@michthemaker/vanjs": patch
3+
---
4+
5+
Add JSDoc documentation for Context API

packages/vanjs/src/utils/context.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import type { ChildDom, State, StateView } from "../van.ts";
22

33
const IS_CONTEXT_OBJECT = Symbol("vanjs_is_context_object");
44

5+
/**
6+
* A context object for sharing reactive state across the component tree.
7+
*
8+
* @template T The type of the context's value.
9+
*/
510
export type Context<T> = {
611
Provider: (
712
stateValue: State<T>,
@@ -11,12 +16,28 @@ export type Context<T> = {
1116

1217
const contextStacks = new Map<Context<any>, StateView<any>[]>();
1318

19+
/**
20+
* Creates a context object for sharing state without prop drilling.
21+
*
22+
* @template T The type of value this context will hold
23+
*
24+
* @example
25+
*
26+
* ```ts
27+
* const ThemeContext = createContext<{ color: string }>();
28+
* const theme = van.state({ color: "blue" });
29+
*
30+
* ThemeContext.Provider(theme, () => {
31+
* const currentTheme = useContext(ThemeContext);
32+
* return div(() => `Color: ${currentTheme.val.color}`);
33+
* });
34+
* ```
35+
*/
1436
export let createContext = <T>(): Context<T> => {
1537
return {
1638
// @ts-ignore
1739
[IS_CONTEXT_OBJECT]: true,
1840
Provider: function provider(stateValue, childrenFn) {
19-
// 1. Get or create stack for this context
2041
if (!contextStacks.has(this)) {
2142
contextStacks.set(this, []);
2243
}
@@ -29,6 +50,18 @@ export let createContext = <T>(): Context<T> => {
2950
};
3051
};
3152

53+
/**
54+
* Returns the current context value from the nearest Provider.
55+
*
56+
* @template T The type of the context's value
57+
*
58+
* @example
59+
*
60+
* ```ts
61+
* const theme = useContext(ThemeContext);
62+
* div(() => `Color: ${theme.val.color}`);
63+
* ```
64+
*/
3265
export let useContext = <T>(context: Context<T>): State<T> => {
3366
// @ts-ignore
3467
if (!context[IS_CONTEXT_OBJECT]) throw new Error("Object is not a `Context`");

packages/vanjs/src/van.ts

Lines changed: 58 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,5 @@
11
import type { ElementEventHandlers } from "./event-handlers.ts";
22

3-
/*
4-
Examples of React jsdoc comment types that clearly states what the API does
5-
/**
6-
* Created by {@link createRef}, or {@link useRef} when passed `null`.
7-
*
8-
* @template T The type of the ref's value.
9-
*
10-
* @example
11-
*
12-
* ```tsx
13-
* const ref = createRef<HTMLDivElement>();
14-
*
15-
* ref.current = document.createElement('div'); // Error
16-
* ```
17-
*/
18-
// interface RefObject<T> {
19-
// /**
20-
// * The current value of the ref.
21-
//
22-
// current: T;
23-
// }
24-
// */
25-
//
26-
//
27-
// // This will technically work if you give a Consumer<T> or Provider<T> but it's deprecated and warns
28-
// /**
29-
// * Accepts a context object (the value returned from `React.createContext`) and returns the current
30-
// * context value, as given by the nearest context provider for the given context.
31-
// *
32-
// * @version 16.8.0
33-
// * @see {@link https://react.dev/reference/react/useContext}
34-
// */
35-
// function useContext<T>(context: Context<T> /*, (not public API) observedBits?: number|boolean */): T;
36-
// /**
37-
// * Returns a stateful value, and a function to update it.
38-
// *
39-
// * @version 16.8.0
40-
// * @see {@link https://react.dev/reference/react/useState}
41-
// */
42-
// function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
43-
//
44-
453
export type {
464
ElementEventHandlers,
475
ReactiveEventHandler,
@@ -56,34 +14,29 @@ export type {
5614
*
5715
* ```ts
5816
* const count = van.state(0);
59-
*
60-
* count.val; // read — tracked as dependency
61-
* count.val = 5; // write — triggers reactive updates
62-
* count.oldVal; // previous value before last update
63-
* count.rawVal; // raw value, no dependency tracking
17+
* count.val = 5; // triggers reactive updates
6418
* ```
6519
*/
6620
export interface State<T> {
6721
/**
68-
* The current value of the state. Reading this property inside a binding or derive
69-
* tracks it as a dependency. Writing to this property triggers reactive updates.
22+
* The current value of the state. Reading tracks it as a dependency, writing triggers updates.
7023
*/
7124
val: T;
7225

7326
/**
74-
* The previous value before the last update. Reading this property tracks it as a dependency.
27+
* The previous value before the last update.
7528
*/
7629
readonly oldVal: T;
7730

7831
/**
79-
* The raw value without dependency tracking. Use this to read the value without
80-
* creating a reactive dependency.
32+
* The raw value without dependency tracking.
8133
*/
8234
readonly rawVal: T;
8335
}
8436

85-
// Defining readonly view of State<T> for covariance.
86-
// Basically we want StateView<string> to implement StateView<string | number>
37+
/**
38+
* Readonly view of State<T> for covariance.
39+
*/
8740
export type StateView<T> = Readonly<State<T>>;
8841

8942
export type Val<T> = State<T> | T;
@@ -110,6 +63,11 @@ export type PropsWithKnownKeys<ElementType> = Partial<{
11063
: K]: PropValueOrDerived;
11164
}>;
11265

66+
/**
67+
* A mutable ref object that holds a current value.
68+
*
69+
* @template T The type of the ref's value.
70+
*/
11371
export type Ref<T> = { current: T | null };
11472

11573
export type RefProp<T> = { ref?: Ref<T> };
@@ -137,43 +95,84 @@ export type TagFunc<Result extends Element> = (
13795
...rest: readonly ChildDom[]
13896
) => Result;
13997

140-
// HTML Tags - typed for all known HTML elements
98+
/**
99+
* HTML tag functions typed for all known HTML elements.
100+
*/
141101
type HTMLTags = Readonly<Record<string, TagFunc<Element>>> & {
142102
[K in keyof HTMLElementTagNameMap]: TagFunc<HTMLElementTagNameMap[K]>;
143103
};
144104

145-
// SVG Tags - typed for all known SVG elements
105+
/**
106+
* SVG tag functions typed for all known SVG elements.
107+
*/
146108
type SVGTags = Readonly<Record<string, TagFunc<SVGElement>>> & {
147109
[K in keyof SVGElementTagNameMap]: TagFunc<SVGElementTagNameMap[K]>;
148110
};
149111

150-
// MathML Tags - typed for all known MathML elements
112+
/**
113+
* MathML tag functions typed for all known MathML elements.
114+
*/
151115
type MathMLTags = Readonly<Record<string, TagFunc<MathMLElement>>> & {
152116
[K in keyof MathMLElementTagNameMap]: TagFunc<MathMLElementTagNameMap[K]>;
153117
};
154118

155-
// Namespace URIs
156119
type SVGNamespaceURI = "http://www.w3.org/2000/svg";
157120
type MathMLNamespaceURI = "http://www.w3.org/1998/Math/MathML";
158121

159-
// Namespace function overloads
122+
/**
123+
* Creates tag functions for elements in a specific namespace.
124+
*/
160125
type NamespacedTags = {
161126
(namespaceURI: SVGNamespaceURI): SVGTags;
162127
(namespaceURI: MathMLNamespaceURI): MathMLTags;
163128
(namespaceURI: string): Readonly<Record<string, TagFunc<Element>>>;
164129
};
165130

131+
/**
132+
* Creates a reactive state object.
133+
*
134+
* @template T The type of the state's value.
135+
*
136+
* @example
137+
*
138+
* ```ts
139+
* const count = van.state(0);
140+
* const name = van.state("Alice");
141+
* ```
142+
*/
166143
declare function state<T>(): State<T>;
167144
declare function state<T>(initVal: T): State<T>;
168145

146+
/**
147+
* The main VanJS interface.
148+
*/
169149
export interface Van {
150+
/**
151+
* Creates a reactive state object.
152+
*/
170153
readonly state: typeof state;
154+
155+
/**
156+
* Creates derived state from a function.
157+
*/
171158
readonly derive: <T>(f: () => T) => State<T>;
159+
160+
/**
161+
* Adds child elements to a DOM node.
162+
*/
172163
readonly add: (
173164
dom: Element | DocumentFragment,
174165
...children: readonly ChildDom[]
175166
) => Element;
167+
168+
/**
169+
* Tag functions for creating HTML, SVG, and MathML elements.
170+
*/
176171
readonly tags: HTMLTags & NamespacedTags;
172+
173+
/**
174+
* Hydrates existing DOM with VanJS reactivity.
175+
*/
177176
readonly hydrate: <T extends Node>(
178177
dom: T,
179178
f: (dom: T) => T | null | undefined

0 commit comments

Comments
 (0)