@mizchi/luna 0.1.4
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 +126 -0
- package/dom.d.ts +2 -0
- package/dom.js +2 -0
- package/index.d.ts +564 -0
- package/index.js +785 -0
- package/jsx-dev-runtime.d.ts +2 -0
- package/jsx-dev-runtime.js +2 -0
- package/jsx-runtime.d.ts +123 -0
- package/jsx-runtime.js +119 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# @mizchi/luna
|
|
2
|
+
|
|
3
|
+
A lightweight reactive UI library with SolidJS-compatible API. Implemented in MoonBit.
|
|
4
|
+
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
- [API Reference](https://luna.mizchi.workers.dev/luna/api/js/)
|
|
8
|
+
- [Tutorial](https://luna.mizchi.workers.dev/luna/tutorial/js/)
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @mizchi/luna
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
### TypeScript + Vite
|
|
19
|
+
|
|
20
|
+
**tsconfig.json:**
|
|
21
|
+
|
|
22
|
+
Set `"jsxImportSource": "@mizchi/luna"`
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"compilerOptions": {
|
|
27
|
+
//...
|
|
28
|
+
"jsxImportSource": "@mizchi/luna",
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**vite.config.ts:**
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { defineConfig } from 'vite';
|
|
37
|
+
|
|
38
|
+
export default defineConfig({});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Basic Usage
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { createSignal, createMemo, render } from '@mizchi/luna';
|
|
45
|
+
|
|
46
|
+
function Counter() {
|
|
47
|
+
const [count, setCount] = createSignal(0);
|
|
48
|
+
const doubled = createMemo(() => count() * 2);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div>
|
|
52
|
+
<p>Count: {count}</p>
|
|
53
|
+
<p>Doubled: {doubled}</p>
|
|
54
|
+
<button onClick={() => setCount(c => c + 1)}>+</button>
|
|
55
|
+
<button onClick={() => setCount(c => c - 1)}>-</button>
|
|
56
|
+
<button onClick={() => setCount(0)}>Reset</button>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
render(document.getElementById('app')!, <Counter />);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API
|
|
65
|
+
|
|
66
|
+
### Signal
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { createSignal, createMemo, createEffect } from '@mizchi/luna';
|
|
70
|
+
|
|
71
|
+
// Signal: reactive value
|
|
72
|
+
const [count, setCount] = createSignal(0);
|
|
73
|
+
count(); // get value
|
|
74
|
+
setCount(5); // set value
|
|
75
|
+
setCount(c => c + 1); // update with function
|
|
76
|
+
|
|
77
|
+
// Memo: derived value
|
|
78
|
+
const doubled = createMemo(() => count() * 2);
|
|
79
|
+
|
|
80
|
+
// Effect: side effects
|
|
81
|
+
createEffect(() => {
|
|
82
|
+
console.log('Count changed:', count());
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Components
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { For, Show } from '@mizchi/luna';
|
|
90
|
+
|
|
91
|
+
// For: list rendering
|
|
92
|
+
<For each={items}>
|
|
93
|
+
{(item) => <li>{item.name}</li>}
|
|
94
|
+
</For>
|
|
95
|
+
|
|
96
|
+
// Show: conditional rendering
|
|
97
|
+
<Show when={() => isVisible()}>
|
|
98
|
+
<div>Visible content</div>
|
|
99
|
+
</Show>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Styles
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// Object syntax (camelCase → kebab-case auto-conversion)
|
|
106
|
+
<div style={{ backgroundColor: 'red', maxWidth: '600px' }}>
|
|
107
|
+
styled content
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
// String syntax
|
|
111
|
+
<div style="color: blue; margin: 10px">
|
|
112
|
+
styled content
|
|
113
|
+
</div>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Event Handlers
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
<button onClick={() => console.log('clicked')}>Click</button>
|
|
120
|
+
<input onInput={(e) => setValue(e.target.value)} />
|
|
121
|
+
<form onSubmit={(e) => { e.preventDefault(); submit(); }}>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
package/dom.d.ts
ADDED
package/dom.js
ADDED
package/index.d.ts
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SolidJS-compatible TypeScript definitions for Luna
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// MoonBit tuple type - exported as object with _0 and _1 properties
|
|
6
|
+
export type MoonBitTuple<A, B> = { _0: A; _1: B };
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Signal API (SolidJS-style)
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/** Signal getter function */
|
|
13
|
+
export type Accessor<T> = () => T;
|
|
14
|
+
|
|
15
|
+
/** Signal setter function - accepts value or updater function */
|
|
16
|
+
export type Setter<T> = (value: T | ((prev: T) => T)) => void;
|
|
17
|
+
|
|
18
|
+
/** Signal tuple [getter, setter] */
|
|
19
|
+
export type Signal<T> = [Accessor<T>, Setter<T>];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a reactive signal (SolidJS-style)
|
|
23
|
+
* @example
|
|
24
|
+
* const [count, setCount] = createSignal(0);
|
|
25
|
+
* count(); // 0
|
|
26
|
+
* setCount(1);
|
|
27
|
+
* setCount(c => c + 1);
|
|
28
|
+
*/
|
|
29
|
+
export function createSignal<T>(initialValue: T): Signal<T>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a reactive effect (SolidJS-style)
|
|
33
|
+
* @example
|
|
34
|
+
* createEffect(() => console.log(count()));
|
|
35
|
+
*/
|
|
36
|
+
export function createEffect(fn: () => void): () => void;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a memoized computed value (SolidJS-style)
|
|
40
|
+
* @example
|
|
41
|
+
* const doubled = createMemo(() => count() * 2);
|
|
42
|
+
*/
|
|
43
|
+
export function createMemo<T>(compute: () => T): Accessor<T>;
|
|
44
|
+
|
|
45
|
+
/** Start a batch update */
|
|
46
|
+
export function batchStart(): void;
|
|
47
|
+
|
|
48
|
+
/** End a batch update and run pending effects */
|
|
49
|
+
export function batchEnd(): void;
|
|
50
|
+
|
|
51
|
+
/** Run a function in a batch - all signal updates are batched */
|
|
52
|
+
export function batch<T>(fn: () => T): T;
|
|
53
|
+
|
|
54
|
+
/** Run a function without tracking dependencies (SolidJS: untrack) */
|
|
55
|
+
export function untrack<T>(fn: () => T): T;
|
|
56
|
+
|
|
57
|
+
/** Register a cleanup function inside an effect */
|
|
58
|
+
export function onCleanup(cleanup: () => void): void;
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Utility functions (SolidJS-style)
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Explicit dependency tracking helper (SolidJS-style)
|
|
66
|
+
* Wraps a function to explicitly specify which signals to track
|
|
67
|
+
* @example
|
|
68
|
+
* createEffect(on(count, (value, prev) => console.log(value, prev)));
|
|
69
|
+
* createEffect(on([a, b], ([a, b]) => console.log(a, b)));
|
|
70
|
+
*/
|
|
71
|
+
export function on<T, U>(
|
|
72
|
+
deps: Accessor<T>,
|
|
73
|
+
fn: (input: T, prevInput: T | undefined, prevValue: U | undefined) => U,
|
|
74
|
+
options?: { defer?: boolean }
|
|
75
|
+
): () => U | undefined;
|
|
76
|
+
export function on<T extends readonly Accessor<any>[], U>(
|
|
77
|
+
deps: T,
|
|
78
|
+
fn: (
|
|
79
|
+
input: { [K in keyof T]: T[K] extends Accessor<infer V> ? V : never },
|
|
80
|
+
prevInput: { [K in keyof T]: T[K] extends Accessor<infer V> ? V : never } | undefined,
|
|
81
|
+
prevValue: U | undefined
|
|
82
|
+
) => U,
|
|
83
|
+
options?: { defer?: boolean }
|
|
84
|
+
): () => U | undefined;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Merge multiple props objects (SolidJS-style)
|
|
88
|
+
* Event handlers and refs are merged, other props are overwritten
|
|
89
|
+
* @example
|
|
90
|
+
* const merged = mergeProps(defaultProps, props);
|
|
91
|
+
*/
|
|
92
|
+
export function mergeProps<T extends object>(...sources: (Partial<T> | undefined)[]): T;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Split props into multiple objects based on key lists (SolidJS-style)
|
|
96
|
+
* @example
|
|
97
|
+
* const [local, others] = splitProps(props, ["class", "style"]);
|
|
98
|
+
*/
|
|
99
|
+
export function splitProps<T extends object, K extends (keyof T)[]>(
|
|
100
|
+
props: T,
|
|
101
|
+
...keys: K[]
|
|
102
|
+
): [...{ [I in keyof K]: Pick<T, K[I] extends (keyof T)[] ? K[I][number] : never> }, Omit<T, K[number][number]>];
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// Owner-based scope management
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
/** Opaque Owner type */
|
|
109
|
+
export interface Owner {
|
|
110
|
+
readonly __brand: unique symbol;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Create a new reactive root scope */
|
|
114
|
+
export function createRoot<T>(fn: (dispose: () => void) => T): T;
|
|
115
|
+
|
|
116
|
+
/** Get the current owner (if any) */
|
|
117
|
+
export function getOwner(): Owner | undefined;
|
|
118
|
+
|
|
119
|
+
/** Run a function with a specific owner as current */
|
|
120
|
+
export function runWithOwner<T>(owner: Owner, fn: () => T): T;
|
|
121
|
+
|
|
122
|
+
/** Check if currently inside an owner scope */
|
|
123
|
+
export function hasOwner(): boolean;
|
|
124
|
+
|
|
125
|
+
/** Run a function once (SolidJS-style onMount) */
|
|
126
|
+
export function onMount(fn: () => void): void;
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// DOM API
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
/** Opaque DOM Node type */
|
|
133
|
+
export interface Node {
|
|
134
|
+
readonly __brand: unique symbol;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Event handler types */
|
|
138
|
+
export type MouseEventHandler = (event: MouseEvent) => void;
|
|
139
|
+
export type InputEventHandler = (event: InputEvent) => void;
|
|
140
|
+
export type KeyboardEventHandler = (event: KeyboardEvent) => void;
|
|
141
|
+
export type FocusEventHandler = (event: FocusEvent) => void;
|
|
142
|
+
export type FormEventHandler = (event: Event) => void;
|
|
143
|
+
export type ChangeEventHandler = (event: Event) => void;
|
|
144
|
+
|
|
145
|
+
/** HandlerMap builder for event handlers (method chaining) */
|
|
146
|
+
export interface HandlerMap {
|
|
147
|
+
click(handler: MouseEventHandler): HandlerMap;
|
|
148
|
+
dblclick(handler: MouseEventHandler): HandlerMap;
|
|
149
|
+
input(handler: InputEventHandler): HandlerMap;
|
|
150
|
+
change(handler: ChangeEventHandler): HandlerMap;
|
|
151
|
+
submit(handler: FormEventHandler): HandlerMap;
|
|
152
|
+
keydown(handler: KeyboardEventHandler): HandlerMap;
|
|
153
|
+
keyup(handler: KeyboardEventHandler): HandlerMap;
|
|
154
|
+
keypress(handler: KeyboardEventHandler): HandlerMap;
|
|
155
|
+
focus(handler: FocusEventHandler): HandlerMap;
|
|
156
|
+
blur(handler: FocusEventHandler): HandlerMap;
|
|
157
|
+
mouseenter(handler: MouseEventHandler): HandlerMap;
|
|
158
|
+
mouseleave(handler: MouseEventHandler): HandlerMap;
|
|
159
|
+
mouseover(handler: MouseEventHandler): HandlerMap;
|
|
160
|
+
mouseout(handler: MouseEventHandler): HandlerMap;
|
|
161
|
+
mousedown(handler: MouseEventHandler): HandlerMap;
|
|
162
|
+
mouseup(handler: MouseEventHandler): HandlerMap;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Create event handler map builder */
|
|
166
|
+
export function events(): HandlerMap;
|
|
167
|
+
|
|
168
|
+
// Text
|
|
169
|
+
export function text(content: string): Node;
|
|
170
|
+
export function textDyn(getter: () => string): Node;
|
|
171
|
+
|
|
172
|
+
// Rendering
|
|
173
|
+
export function render(container: Element, node: Node): void;
|
|
174
|
+
export function mount(container: Element, node: Node): void;
|
|
175
|
+
export function show(condition: () => boolean, render: () => Node): Node;
|
|
176
|
+
|
|
177
|
+
// List rendering (low-level)
|
|
178
|
+
export function forEach<T>(
|
|
179
|
+
items: () => T[],
|
|
180
|
+
renderItem: (item: T, index: number) => Node
|
|
181
|
+
): Node;
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// SolidJS-compatible Components
|
|
185
|
+
// ============================================================================
|
|
186
|
+
|
|
187
|
+
/** For component props */
|
|
188
|
+
export interface ForProps<T, U extends Node> {
|
|
189
|
+
each: Accessor<T[]> | T[];
|
|
190
|
+
fallback?: Node;
|
|
191
|
+
children: (item: T, index: Accessor<number>) => U;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* For component for list rendering (SolidJS-style)
|
|
196
|
+
* @example
|
|
197
|
+
* <For each={items}>{(item, index) => <div>{item}</div>}</For>
|
|
198
|
+
*/
|
|
199
|
+
export function For<T, U extends Node>(props: ForProps<T, U>): Node;
|
|
200
|
+
|
|
201
|
+
/** Show component props */
|
|
202
|
+
export interface ShowProps<T> {
|
|
203
|
+
when: T | Accessor<T>;
|
|
204
|
+
fallback?: Node;
|
|
205
|
+
children: Node | ((item: NonNullable<T>) => Node);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Show component for conditional rendering (SolidJS-style)
|
|
210
|
+
* Note: fallback prop is not yet supported (Luna limitation)
|
|
211
|
+
* @example
|
|
212
|
+
* <Show when={isVisible}><div>Visible!</div></Show>
|
|
213
|
+
*/
|
|
214
|
+
export function Show<T>(props: ShowProps<T>): Node;
|
|
215
|
+
|
|
216
|
+
/** Index component props */
|
|
217
|
+
export interface IndexProps<T, U extends Node> {
|
|
218
|
+
each: Accessor<T[]> | T[];
|
|
219
|
+
fallback?: Node;
|
|
220
|
+
children: (item: Accessor<T>, index: number) => U;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Index component for index-based list rendering (SolidJS-style)
|
|
225
|
+
* Unlike For which tracks items by reference, Index tracks by index position
|
|
226
|
+
* @example
|
|
227
|
+
* <Index each={items}>{(item, index) => <div>{item()}</div>}</Index>
|
|
228
|
+
*/
|
|
229
|
+
export function Index<T, U extends Node>(props: IndexProps<T, U>): Node;
|
|
230
|
+
|
|
231
|
+
/** Provider component props */
|
|
232
|
+
export interface ProviderProps<T> {
|
|
233
|
+
context: Context<T>;
|
|
234
|
+
value: T;
|
|
235
|
+
children: Node | (() => Node);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Provider component for Context (SolidJS-style)
|
|
240
|
+
* @example
|
|
241
|
+
* <Provider context={ThemeContext} value="dark"><App /></Provider>
|
|
242
|
+
*/
|
|
243
|
+
export function Provider<T>(props: ProviderProps<T>): Node;
|
|
244
|
+
|
|
245
|
+
/** Match component result (internal) */
|
|
246
|
+
export interface MatchResult<T> {
|
|
247
|
+
readonly __isMatch: true;
|
|
248
|
+
when: () => boolean;
|
|
249
|
+
children: T | (() => T);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Switch component props */
|
|
253
|
+
export interface SwitchProps {
|
|
254
|
+
fallback?: Node;
|
|
255
|
+
children: MatchResult<Node>[];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Switch component for conditional rendering with multiple branches (SolidJS-style)
|
|
260
|
+
* @example
|
|
261
|
+
* <Switch fallback={<div>Not found</div>}>
|
|
262
|
+
* <Match when={isA}><A /></Match>
|
|
263
|
+
* <Match when={isB}><B /></Match>
|
|
264
|
+
* </Switch>
|
|
265
|
+
*/
|
|
266
|
+
export function Switch(props: SwitchProps): Node;
|
|
267
|
+
|
|
268
|
+
/** Match component props */
|
|
269
|
+
export interface MatchProps<T> {
|
|
270
|
+
when: T | Accessor<T>;
|
|
271
|
+
children: Node | ((item: NonNullable<T>) => Node);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Match component for use inside Switch (SolidJS-style)
|
|
276
|
+
*/
|
|
277
|
+
export function Match<T>(props: MatchProps<T>): MatchResult<Node>;
|
|
278
|
+
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// Portal API (SolidJS-style)
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
/** Portal component props */
|
|
284
|
+
export interface PortalProps {
|
|
285
|
+
/** Target element or CSS selector to mount to (defaults to document.body) */
|
|
286
|
+
mount?: Element | string;
|
|
287
|
+
/** Whether to use Shadow DOM for encapsulation */
|
|
288
|
+
useShadow?: boolean;
|
|
289
|
+
/** Children to render in the portal */
|
|
290
|
+
children: Node | Node[] | (() => Node);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Portal component for rendering outside the component tree (SolidJS-style)
|
|
295
|
+
* Teleports children to a different DOM location
|
|
296
|
+
* @example
|
|
297
|
+
* Portal({ children: modal() }) // renders to body
|
|
298
|
+
* Portal({ mount: "#modal-root", children: modal() }) // renders to #modal-root
|
|
299
|
+
* Portal({ useShadow: true, children: modal() }) // renders with Shadow DOM
|
|
300
|
+
*/
|
|
301
|
+
export function Portal(props: PortalProps): Node;
|
|
302
|
+
|
|
303
|
+
/** Low-level portal to body */
|
|
304
|
+
export function portalToBody(children: Node[]): Node;
|
|
305
|
+
|
|
306
|
+
/** Low-level portal to CSS selector */
|
|
307
|
+
export function portalToSelector(selector: string, children: Node[]): Node;
|
|
308
|
+
|
|
309
|
+
/** Low-level portal with Shadow DOM */
|
|
310
|
+
export function portalWithShadow(children: Node[]): Node;
|
|
311
|
+
|
|
312
|
+
/** Low-level portal to element with Shadow DOM */
|
|
313
|
+
export function portalToElementWithShadow(mount: Element, children: Node[]): Node;
|
|
314
|
+
|
|
315
|
+
// Low-level element creation (MoonBit API)
|
|
316
|
+
export function jsx(
|
|
317
|
+
tag: string,
|
|
318
|
+
attrs: MoonBitTuple<string, unknown>[],
|
|
319
|
+
children: Node[]
|
|
320
|
+
): Node;
|
|
321
|
+
export function jsxs(
|
|
322
|
+
tag: string,
|
|
323
|
+
attrs: MoonBitTuple<string, unknown>[],
|
|
324
|
+
children: Node[]
|
|
325
|
+
): Node;
|
|
326
|
+
/** Fragment function - returns children wrapped in a DocumentFragment */
|
|
327
|
+
export function Fragment(children: Node[]): Node;
|
|
328
|
+
|
|
329
|
+
/** Fragment symbol for JSX */
|
|
330
|
+
export const FragmentSymbol: unique symbol;
|
|
331
|
+
|
|
332
|
+
// Element creation (low-level)
|
|
333
|
+
export function createElement(
|
|
334
|
+
tag: string,
|
|
335
|
+
attrs: MoonBitTuple<string, unknown>[],
|
|
336
|
+
children: Node[]
|
|
337
|
+
): Node;
|
|
338
|
+
|
|
339
|
+
// ============================================================================
|
|
340
|
+
// Context API
|
|
341
|
+
// ============================================================================
|
|
342
|
+
|
|
343
|
+
/** Opaque Context type */
|
|
344
|
+
export interface Context<T> {
|
|
345
|
+
readonly __brand: unique symbol;
|
|
346
|
+
readonly __type: T;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Create a new context with a default value */
|
|
350
|
+
export function createContext<T>(defaultValue: T): Context<T>;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Provide a context value for the current Owner scope and its descendants.
|
|
354
|
+
* Context values are Owner-based (component-tree-scoped), similar to SolidJS.
|
|
355
|
+
*/
|
|
356
|
+
export function provide<T, R>(ctx: Context<T>, value: T, fn: () => R): R;
|
|
357
|
+
|
|
358
|
+
/** Use a context value - returns the current provided value or default */
|
|
359
|
+
export function useContext<T>(ctx: Context<T>): T;
|
|
360
|
+
|
|
361
|
+
// ============================================================================
|
|
362
|
+
// Resource API (SolidJS-style)
|
|
363
|
+
// ============================================================================
|
|
364
|
+
|
|
365
|
+
/** Resource state */
|
|
366
|
+
export type ResourceState = "unresolved" | "pending" | "ready" | "errored";
|
|
367
|
+
|
|
368
|
+
/** Resource accessor with properties */
|
|
369
|
+
export interface ResourceAccessor<T> {
|
|
370
|
+
(): T | undefined;
|
|
371
|
+
readonly loading: boolean;
|
|
372
|
+
readonly error: string | undefined;
|
|
373
|
+
readonly state: ResourceState;
|
|
374
|
+
readonly latest: T | undefined;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Resource actions */
|
|
378
|
+
export interface ResourceActions {
|
|
379
|
+
refetch: () => void;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Create a Resource from a callback-based fetcher (SolidJS-style)
|
|
384
|
+
* @example
|
|
385
|
+
* const [data, { refetch }] = createResource((resolve, reject) => {
|
|
386
|
+
* fetch('/api').then(r => r.json()).then(resolve).catch(e => reject(e.message));
|
|
387
|
+
* });
|
|
388
|
+
* data(); // value or undefined
|
|
389
|
+
* data.loading; // boolean
|
|
390
|
+
* data.error; // string or undefined
|
|
391
|
+
*/
|
|
392
|
+
export function createResource<T>(
|
|
393
|
+
fetcher: (resolve: (value: T) => void, reject: (error: string) => void) => void
|
|
394
|
+
): [ResourceAccessor<T>, ResourceActions];
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Create a deferred Resource (SolidJS-style)
|
|
398
|
+
* Returns [accessor, resolve, reject]
|
|
399
|
+
*/
|
|
400
|
+
export function createDeferred<T>(): [
|
|
401
|
+
ResourceAccessor<T>,
|
|
402
|
+
(value: T) => void,
|
|
403
|
+
(error: string) => void
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
// Low-level resource helpers
|
|
407
|
+
export function resourceGet<T>(resource: any): any;
|
|
408
|
+
export function resourcePeek<T>(resource: any): any;
|
|
409
|
+
export function resourceRefetch<T>(resource: any): void;
|
|
410
|
+
export function resourceIsPending<T>(resource: any): boolean;
|
|
411
|
+
export function resourceIsSuccess<T>(resource: any): boolean;
|
|
412
|
+
export function resourceIsFailure<T>(resource: any): boolean;
|
|
413
|
+
export function resourceValue<T>(resource: any): T | undefined;
|
|
414
|
+
export function resourceError<T>(resource: any): string | undefined;
|
|
415
|
+
export function stateIsPending<T>(state: any): boolean;
|
|
416
|
+
export function stateIsSuccess<T>(state: any): boolean;
|
|
417
|
+
export function stateIsFailure<T>(state: any): boolean;
|
|
418
|
+
export function stateValue<T>(state: any): T | undefined;
|
|
419
|
+
export function stateError<T>(state: any): string | undefined;
|
|
420
|
+
|
|
421
|
+
// ============================================================================
|
|
422
|
+
// Timer utilities
|
|
423
|
+
// ============================================================================
|
|
424
|
+
|
|
425
|
+
/** Debounce a signal */
|
|
426
|
+
export function debounced<T>(signal: Signal<T>, delayMs: number): Signal<T>;
|
|
427
|
+
|
|
428
|
+
// ============================================================================
|
|
429
|
+
// Store API (SolidJS-style)
|
|
430
|
+
// ============================================================================
|
|
431
|
+
|
|
432
|
+
/** Path segment type for setState */
|
|
433
|
+
type PathSegment = string | number;
|
|
434
|
+
|
|
435
|
+
/** Store setter function type */
|
|
436
|
+
export interface SetStoreFunction<T> {
|
|
437
|
+
/** Update value at path */
|
|
438
|
+
<K1 extends keyof T>(key1: K1, value: T[K1] | ((prev: T[K1]) => T[K1])): void;
|
|
439
|
+
<K1 extends keyof T, K2 extends keyof T[K1]>(
|
|
440
|
+
key1: K1,
|
|
441
|
+
key2: K2,
|
|
442
|
+
value: T[K1][K2] | ((prev: T[K1][K2]) => T[K1][K2])
|
|
443
|
+
): void;
|
|
444
|
+
<K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2]>(
|
|
445
|
+
key1: K1,
|
|
446
|
+
key2: K2,
|
|
447
|
+
key3: K3,
|
|
448
|
+
value: T[K1][K2][K3] | ((prev: T[K1][K2][K3]) => T[K1][K2][K3])
|
|
449
|
+
): void;
|
|
450
|
+
/** Merge object at root */
|
|
451
|
+
(value: Partial<T>): void;
|
|
452
|
+
/** Generic path-based update */
|
|
453
|
+
(...args: [...PathSegment[], unknown]): void;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** Store tuple type */
|
|
457
|
+
export type Store<T> = [T, SetStoreFunction<T>];
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Creates a reactive store with nested property tracking (SolidJS-style)
|
|
461
|
+
* @example
|
|
462
|
+
* const [state, setState] = createStore({ count: 0, user: { name: "John" } });
|
|
463
|
+
*
|
|
464
|
+
* // Read (reactive - tracks dependencies)
|
|
465
|
+
* state.count
|
|
466
|
+
* state.user.name
|
|
467
|
+
*
|
|
468
|
+
* // Update by path
|
|
469
|
+
* setState("count", 1);
|
|
470
|
+
* setState("user", "name", "Jane");
|
|
471
|
+
*
|
|
472
|
+
* // Functional update
|
|
473
|
+
* setState("count", c => c + 1);
|
|
474
|
+
*
|
|
475
|
+
* // Object merge at path
|
|
476
|
+
* setState("user", { name: "Jane", age: 30 });
|
|
477
|
+
*/
|
|
478
|
+
export function createStore<T extends object>(initialValue: T): Store<T>;
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Produce helper for immer-style mutations (SolidJS-style)
|
|
482
|
+
* @example
|
|
483
|
+
* setState("user", produce(user => { user.name = "Jane"; }));
|
|
484
|
+
*/
|
|
485
|
+
export function produce<T>(fn: (draft: T) => void): (state: T) => T;
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Reconcile helper for efficient array/object updates (SolidJS-style)
|
|
489
|
+
* Replaces the entire value at the path
|
|
490
|
+
* @example
|
|
491
|
+
* setState("items", reconcile(newItems));
|
|
492
|
+
*/
|
|
493
|
+
export function reconcile<T>(value: T): (state: T) => T;
|
|
494
|
+
|
|
495
|
+
// ============================================================================
|
|
496
|
+
// Router API
|
|
497
|
+
// ============================================================================
|
|
498
|
+
|
|
499
|
+
/** Route definition - Page route */
|
|
500
|
+
export interface PageRoute {
|
|
501
|
+
readonly $tag: 0;
|
|
502
|
+
readonly path: string;
|
|
503
|
+
readonly component: string;
|
|
504
|
+
readonly title: string;
|
|
505
|
+
readonly meta: MoonBitTuple<string, string>[];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** Route definition union type */
|
|
509
|
+
export type Routes = PageRoute;
|
|
510
|
+
|
|
511
|
+
export function routePage(path: string, component: string): Routes;
|
|
512
|
+
export function routePageTitled(path: string, component: string, title: string): Routes;
|
|
513
|
+
export function routePageFull(path: string, component: string, title: string, meta: MoonBitTuple<string, string>[]): Routes;
|
|
514
|
+
|
|
515
|
+
/** Opaque BrowserRouter type */
|
|
516
|
+
export interface BrowserRouter {
|
|
517
|
+
readonly __brand: unique symbol;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** Compiled route info */
|
|
521
|
+
export interface CompiledRoute {
|
|
522
|
+
readonly pattern: string;
|
|
523
|
+
readonly param_names: string[];
|
|
524
|
+
readonly component: string;
|
|
525
|
+
readonly layouts: string[];
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Route match result */
|
|
529
|
+
export interface RoutesMatch {
|
|
530
|
+
readonly route: CompiledRoute;
|
|
531
|
+
readonly params: MoonBitTuple<string, string>[];
|
|
532
|
+
readonly query: MoonBitTuple<string, string>[];
|
|
533
|
+
readonly path: string;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export function createRouter(routes: Routes[], base?: string): BrowserRouter;
|
|
537
|
+
export function routerNavigate(router: BrowserRouter, path: string): void;
|
|
538
|
+
export function routerReplace(router: BrowserRouter, path: string): void;
|
|
539
|
+
export function routerGetPath(router: BrowserRouter): string;
|
|
540
|
+
export function routerGetMatch(router: BrowserRouter): RoutesMatch | undefined;
|
|
541
|
+
export function routerGetBase(router: BrowserRouter): string;
|
|
542
|
+
|
|
543
|
+
// ============================================================================
|
|
544
|
+
// Legacy API (for backwards compatibility)
|
|
545
|
+
// ============================================================================
|
|
546
|
+
|
|
547
|
+
/** @deprecated Use createSignal()[0] instead */
|
|
548
|
+
export function get<T>(signal: any): T;
|
|
549
|
+
/** @deprecated Use createSignal()[1] instead */
|
|
550
|
+
export function set<T>(signal: any, value: T): void;
|
|
551
|
+
/** @deprecated Use createSignal()[1] with function instead */
|
|
552
|
+
export function update<T>(signal: any, fn: (current: T) => T): void;
|
|
553
|
+
/** @deprecated */
|
|
554
|
+
export function peek<T>(signal: any): T;
|
|
555
|
+
/** @deprecated */
|
|
556
|
+
export function subscribe<T>(signal: any, callback: (value: T) => void): () => void;
|
|
557
|
+
/** @deprecated */
|
|
558
|
+
export function map<T, U>(signal: any, fn: (value: T) => U): () => U;
|
|
559
|
+
/** @deprecated */
|
|
560
|
+
export function combine<A, B, R>(a: any, b: any, fn: (a: A, b: B) => R): () => R;
|
|
561
|
+
/** @deprecated Use createEffect instead */
|
|
562
|
+
export function effect(fn: () => void): () => void;
|
|
563
|
+
/** @deprecated Use untrack instead */
|
|
564
|
+
export function runUntracked<T>(fn: () => T): T;
|