@hedystia/view 2.1.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.
- package/dist/constants.cjs +13 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/constants.mjs +13 -0
- package/dist/constants.mjs.map +1 -0
- package/dist/context/context.cjs +51 -0
- package/dist/context/context.cjs.map +1 -0
- package/dist/context/context.d.cts +25 -0
- package/dist/context/context.d.mts +25 -0
- package/dist/context/context.mjs +50 -0
- package/dist/context/context.mjs.map +1 -0
- package/dist/fetch/resource.cjs +89 -0
- package/dist/fetch/resource.cjs.map +1 -0
- package/dist/fetch/resource.d.cts +14 -0
- package/dist/fetch/resource.d.mts +14 -0
- package/dist/fetch/resource.mjs +88 -0
- package/dist/fetch/resource.mjs.map +1 -0
- package/dist/index.cjs +58 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.mts +15 -0
- package/dist/index.mjs +14 -0
- package/dist/jsx/element.cjs +201 -0
- package/dist/jsx/element.cjs.map +1 -0
- package/dist/jsx/element.d.cts +48 -0
- package/dist/jsx/element.d.mts +48 -0
- package/dist/jsx/element.mjs +199 -0
- package/dist/jsx/element.mjs.map +1 -0
- package/dist/jsx-dev-runtime.cjs +40 -0
- package/dist/jsx-dev-runtime.cjs.map +1 -0
- package/dist/jsx-dev-runtime.d.cts +21 -0
- package/dist/jsx-dev-runtime.d.mts +21 -0
- package/dist/jsx-dev-runtime.mjs +36 -0
- package/dist/jsx-dev-runtime.mjs.map +1 -0
- package/dist/jsx-runtime.cjs +5 -0
- package/dist/jsx-runtime.d.cts +3 -0
- package/dist/jsx-runtime.d.mts +3 -0
- package/dist/jsx-runtime.mjs +2 -0
- package/dist/jsx.d.cts +942 -0
- package/dist/jsx.d.mts +942 -0
- package/dist/lifecycle/hooks.cjs +56 -0
- package/dist/lifecycle/hooks.cjs.map +1 -0
- package/dist/lifecycle/hooks.d.cts +37 -0
- package/dist/lifecycle/hooks.d.mts +37 -0
- package/dist/lifecycle/hooks.mjs +54 -0
- package/dist/lifecycle/hooks.mjs.map +1 -0
- package/dist/render/engine.cjs +52 -0
- package/dist/render/engine.cjs.map +1 -0
- package/dist/render/engine.d.cts +31 -0
- package/dist/render/engine.d.mts +31 -0
- package/dist/render/engine.mjs +51 -0
- package/dist/render/engine.mjs.map +1 -0
- package/dist/render/flow.cjs +286 -0
- package/dist/render/flow.cjs.map +1 -0
- package/dist/render/flow.d.cts +64 -0
- package/dist/render/flow.d.mts +64 -0
- package/dist/render/flow.mjs +279 -0
- package/dist/render/flow.mjs.map +1 -0
- package/dist/scheduler/scheduler.cjs +61 -0
- package/dist/scheduler/scheduler.cjs.map +1 -0
- package/dist/scheduler/scheduler.d.cts +31 -0
- package/dist/scheduler/scheduler.d.mts +31 -0
- package/dist/scheduler/scheduler.mjs +59 -0
- package/dist/scheduler/scheduler.mjs.map +1 -0
- package/dist/signal/signal.cjs +387 -0
- package/dist/signal/signal.cjs.map +1 -0
- package/dist/signal/signal.d.cts +44 -0
- package/dist/signal/signal.d.mts +44 -0
- package/dist/signal/signal.mjs +370 -0
- package/dist/signal/signal.mjs.map +1 -0
- package/dist/store/index.cjs +1 -0
- package/dist/store/index.mjs +2 -0
- package/dist/store/store.cjs +94 -0
- package/dist/store/store.cjs.map +1 -0
- package/dist/store/store.d.cts +22 -0
- package/dist/store/store.d.mts +22 -0
- package/dist/store/store.mjs +91 -0
- package/dist/store/store.mjs.map +1 -0
- package/dist/style/computed.cjs +65 -0
- package/dist/style/computed.cjs.map +1 -0
- package/dist/style/computed.d.cts +18 -0
- package/dist/style/computed.d.mts +18 -0
- package/dist/style/computed.mjs +63 -0
- package/dist/style/computed.mjs.map +1 -0
- package/dist/text/text.cjs +74 -0
- package/dist/text/text.cjs.map +1 -0
- package/dist/text/text.d.cts +31 -0
- package/dist/text/text.d.mts +31 -0
- package/dist/text/text.mjs +72 -0
- package/dist/text/text.mjs.map +1 -0
- package/dist/types.d.cts +185 -0
- package/dist/types.d.mts +185 -0
- package/dist/utils/index.cjs +34 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.mjs +33 -0
- package/dist/utils/index.mjs.map +1 -0
- package/dist/watch/watcher.cjs +71 -0
- package/dist/watch/watcher.cjs.map +1 -0
- package/dist/watch/watcher.d.cts +17 -0
- package/dist/watch/watcher.d.mts +17 -0
- package/dist/watch/watcher.mjs +70 -0
- package/dist/watch/watcher.mjs.map +1 -0
- package/package.json +34 -0
- package/readme.md +395 -0
package/readme.md
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# @hedystia/view
|
|
2
|
+
|
|
3
|
+
Reactive UI engine — fine-grained signals, no Virtual DOM, real DOM nodes.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @hedystia/view
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
Configure your `tsconfig.json`:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"compilerOptions": {
|
|
18
|
+
"jsx": "react-jsx",
|
|
19
|
+
"jsxImportSource": "@hedystia/view"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { sig, val, set, mount } from "@hedystia/view";
|
|
26
|
+
|
|
27
|
+
function Counter() {
|
|
28
|
+
const count = sig(0);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div style={{ padding: "16px" }}>
|
|
32
|
+
<h1>Counter: {() => val(count)}</h1>
|
|
33
|
+
<button onClick={() => set(count, val(count) + 1)}>+</button>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
mount(Counter, document.getElementById("root")!);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> Components run **once**. Reactivity comes from wrapping signal reads in `() => ...` accessors.
|
|
42
|
+
|
|
43
|
+
## Signals
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { sig, val, set, update, memo, batch, peek, untrack } from "@hedystia/view";
|
|
47
|
+
|
|
48
|
+
const count = sig(0);
|
|
49
|
+
|
|
50
|
+
// Read (tracked inside reactive contexts)
|
|
51
|
+
val(count); // 0
|
|
52
|
+
|
|
53
|
+
// Write
|
|
54
|
+
set(count, 5);
|
|
55
|
+
|
|
56
|
+
// Update from previous
|
|
57
|
+
update(count, (prev) => prev + 1);
|
|
58
|
+
|
|
59
|
+
// Derived / computed
|
|
60
|
+
const doubled = memo(() => val(count) * 2);
|
|
61
|
+
|
|
62
|
+
// Batch multiple updates into one reactive cycle
|
|
63
|
+
batch(() => {
|
|
64
|
+
set(a, 1);
|
|
65
|
+
set(b, 2);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Read without tracking
|
|
69
|
+
peek(count);
|
|
70
|
+
|
|
71
|
+
// Run without tracking
|
|
72
|
+
untrack(() => val(count));
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### JSX reactive patterns
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
function App() {
|
|
79
|
+
const count = sig(0);
|
|
80
|
+
const doubled = memo(() => val(count) * 2);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div>
|
|
84
|
+
{/* Reactive text — wrap in () => */}
|
|
85
|
+
<span>{() => val(count)}</span>
|
|
86
|
+
<span>Doubled: {() => val(doubled)}</span>
|
|
87
|
+
|
|
88
|
+
{/* Reactive style — pass a function */}
|
|
89
|
+
<div style={() => ({ color: val(count) > 5 ? "red" : "blue" })}>
|
|
90
|
+
Dynamic color
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{/* Reactive prop — pass a function */}
|
|
94
|
+
<input value={() => `Count is ${val(count)}`} />
|
|
95
|
+
|
|
96
|
+
{/* Reactive list — function child returning array */}
|
|
97
|
+
<ul>
|
|
98
|
+
{() => val(items).map((item) => (
|
|
99
|
+
<li>{item.name}</li>
|
|
100
|
+
))}
|
|
101
|
+
</ul>
|
|
102
|
+
|
|
103
|
+
{/* Events */}
|
|
104
|
+
<button onClick={() => set(count, val(count) + 1)}>+</button>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Store
|
|
111
|
+
|
|
112
|
+
Nested reactive state with fine-grained updates:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { store, val, set, patch, snap, reset } from "@hedystia/view";
|
|
116
|
+
|
|
117
|
+
const app = store({
|
|
118
|
+
user: { name: "guest", role: "viewer" },
|
|
119
|
+
theme: "dark",
|
|
120
|
+
count: 0,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
function Profile() {
|
|
124
|
+
return (
|
|
125
|
+
<div>
|
|
126
|
+
<span>{() => val(app.user.name)}</span>
|
|
127
|
+
<button onClick={() => set(app.theme, "light")}>Light mode</button>
|
|
128
|
+
<button onClick={() => patch(app.user, { name: "alice", role: "admin" })}>
|
|
129
|
+
Login
|
|
130
|
+
</button>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Plain snapshot
|
|
136
|
+
const snapshot = snap(app);
|
|
137
|
+
|
|
138
|
+
// Reset to initial values
|
|
139
|
+
reset(app, { user: { name: "guest", role: "viewer" }, theme: "dark", count: 0 });
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Effects
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { on, once } from "@hedystia/view";
|
|
146
|
+
|
|
147
|
+
// Runs whenever count changes
|
|
148
|
+
const dispose = on(
|
|
149
|
+
() => val(count),
|
|
150
|
+
(value, prev) => {
|
|
151
|
+
console.log(`${prev} → ${value}`);
|
|
152
|
+
return () => console.log("cleanup"); // optional cleanup
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Run once then auto-dispose
|
|
157
|
+
once(() => val(count), (value) => {
|
|
158
|
+
console.log("initial:", value);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Stop watching
|
|
162
|
+
dispose();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Data Fetching
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import { sig, val, set, load, action } from "@hedystia/view";
|
|
169
|
+
|
|
170
|
+
const userId = sig(1);
|
|
171
|
+
|
|
172
|
+
const user = load(
|
|
173
|
+
() => val(userId),
|
|
174
|
+
async (id) => {
|
|
175
|
+
const res = await fetch(`/api/users/${id}`);
|
|
176
|
+
return res.json();
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
function UserCard() {
|
|
181
|
+
return (
|
|
182
|
+
<div>
|
|
183
|
+
{() => {
|
|
184
|
+
if (val(user.loading)) return <span>Loading...</span>;
|
|
185
|
+
if (val(user.error)) return <span>Error: {val(user.error)!.message}</span>;
|
|
186
|
+
const data = val(user.data);
|
|
187
|
+
if (!data) return <span>No data</span>;
|
|
188
|
+
return <span>{data.name}</span>;
|
|
189
|
+
}}
|
|
190
|
+
<button onClick={() => set(userId, val(userId) + 1)}>Next user</button>
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Mutations
|
|
196
|
+
const savePost = action(async (data: { title: string }) => {
|
|
197
|
+
const res = await fetch("/api/posts", { method: "POST", body: JSON.stringify(data) });
|
|
198
|
+
return res.json();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
await savePost.run({ title: "Hello" });
|
|
202
|
+
val(savePost.loading); // true while saving
|
|
203
|
+
val(savePost.data); // result when done
|
|
204
|
+
val(savePost.error); // error if failed
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Flow Components
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { Show, For, Index, Switch, Match, Portal, Suspense, ErrorBoundary } from "@hedystia/view";
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Show
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
<div>
|
|
217
|
+
{Show({
|
|
218
|
+
when: () => val(loggedIn),
|
|
219
|
+
fallback: <span>Please log in</span>,
|
|
220
|
+
children: <Dashboard />
|
|
221
|
+
})}
|
|
222
|
+
</div>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### For (keyed list)
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
<ul>
|
|
229
|
+
{For({
|
|
230
|
+
each: () => val(items),
|
|
231
|
+
key: (item) => item.id,
|
|
232
|
+
children: (item, index) => <li>{val(item).name}</li>
|
|
233
|
+
})}
|
|
234
|
+
</ul>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Index (index-based list)
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
<ul>
|
|
241
|
+
{Index({
|
|
242
|
+
each: () => val(items),
|
|
243
|
+
children: (item, index) => <li>#{index}: {val(item)}</li>
|
|
244
|
+
})}
|
|
245
|
+
</ul>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Switch / Match
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
<div>
|
|
252
|
+
{Switch({
|
|
253
|
+
fallback: <span>Not found</span>,
|
|
254
|
+
children: [
|
|
255
|
+
Match({ when: () => val(route) === "home", children: <Home /> }),
|
|
256
|
+
Match({ when: () => val(route) === "settings", children: <Settings /> }),
|
|
257
|
+
]
|
|
258
|
+
})}
|
|
259
|
+
</div>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Portal
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
<div>
|
|
266
|
+
{Portal({ mount: document.body, children: <Modal /> })}
|
|
267
|
+
</div>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Lifecycle
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
import { onMount, onCleanup, onReady } from "@hedystia/view";
|
|
274
|
+
|
|
275
|
+
function Component() {
|
|
276
|
+
onMount(() => {
|
|
277
|
+
console.log("mounted");
|
|
278
|
+
return () => console.log("unmounted");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
onCleanup(() => console.log("cleaning up"));
|
|
282
|
+
onReady(() => console.log("ready"));
|
|
283
|
+
|
|
284
|
+
return <div>Hello</div>;
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Context
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
import { ctx, use } from "@hedystia/view";
|
|
292
|
+
|
|
293
|
+
const ThemeCtx = ctx<{ mode: "dark" | "light"; accent: string }>({
|
|
294
|
+
mode: "dark",
|
|
295
|
+
accent: "#00d9ff",
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
function App() {
|
|
299
|
+
return ThemeCtx.Provider({
|
|
300
|
+
value: { mode: "dark", accent: "#00d9ff" },
|
|
301
|
+
children: <Dashboard />
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function Dashboard() {
|
|
306
|
+
const theme = use(ThemeCtx);
|
|
307
|
+
return <div style={{ color: theme.accent }}>Theme: {theme.mode}</div>;
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Text Measurement
|
|
312
|
+
|
|
313
|
+
Pure-arithmetic text layout without DOM reflow:
|
|
314
|
+
|
|
315
|
+
```tsx
|
|
316
|
+
import { sig, val, set, memo, prepare, layout } from "@hedystia/view";
|
|
317
|
+
|
|
318
|
+
const text = "Hello world, this is a long paragraph.";
|
|
319
|
+
const font = '16px "Helvetica Neue", sans-serif';
|
|
320
|
+
|
|
321
|
+
// Prepare once (measures segments via canvas)
|
|
322
|
+
const prepared = prepare(text, font);
|
|
323
|
+
|
|
324
|
+
const width = sig(300);
|
|
325
|
+
|
|
326
|
+
// Layout at any width — pure arithmetic, ~0.0002ms
|
|
327
|
+
const result = memo(() => layout(prepared, val(width), 24));
|
|
328
|
+
|
|
329
|
+
function TextBlock() {
|
|
330
|
+
return (
|
|
331
|
+
<div>
|
|
332
|
+
<p>Lines: {() => val(result).lineCount}</p>
|
|
333
|
+
<p>Height: {() => val(result).height}px</p>
|
|
334
|
+
</div>
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Style Utilities
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
import { style, merge, toCssString } from "@hedystia/view";
|
|
343
|
+
|
|
344
|
+
const baseStyle = style({ padding: "16px", background: "#1a1a2e" });
|
|
345
|
+
const merged = merge(baseStyle(), { color: "#fff", borderRadius: "8px" });
|
|
346
|
+
const css = toCssString(merged); // "padding: 16px; background: #1a1a2e; color: #fff; ..."
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Scheduler
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
import { tick, nextFrame, forceFlush } from "@hedystia/view";
|
|
353
|
+
|
|
354
|
+
// Schedule for next animation frame
|
|
355
|
+
tick(() => { /* DOM update */ });
|
|
356
|
+
|
|
357
|
+
// Await next frame
|
|
358
|
+
await nextFrame();
|
|
359
|
+
|
|
360
|
+
// Force flush pending callbacks (testing)
|
|
361
|
+
await forceFlush();
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Render
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
import { mount, renderToString } from "@hedystia/view";
|
|
368
|
+
|
|
369
|
+
// Mount to DOM
|
|
370
|
+
const app = mount(App, document.getElementById("root")!);
|
|
371
|
+
app.dispose(); // unmount
|
|
372
|
+
|
|
373
|
+
// SSR
|
|
374
|
+
const html = renderToString(App);
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## API Reference
|
|
378
|
+
|
|
379
|
+
| Category | Functions |
|
|
380
|
+
|----------|-----------|
|
|
381
|
+
| **Signals** | `sig`, `val`, `set`, `update`, `memo`, `batch`, `peek`, `untrack` |
|
|
382
|
+
| **Store** | `store`, `patch`, `reset`, `snap` |
|
|
383
|
+
| **Effects** | `on`, `once` |
|
|
384
|
+
| **Lifecycle** | `onMount`, `onCleanup`, `onReady` |
|
|
385
|
+
| **Context** | `ctx`, `use` |
|
|
386
|
+
| **Fetch** | `load`, `action` |
|
|
387
|
+
| **Flow** | `Show`, `For`, `Index`, `Switch`, `Match`, `Portal`, `Suspense`, `ErrorBoundary` |
|
|
388
|
+
| **Render** | `mount`, `renderToString` |
|
|
389
|
+
| **Text** | `prepare`, `layout`, `reactiveLayout` |
|
|
390
|
+
| **Style** | `style`, `merge`, `toCssString` |
|
|
391
|
+
| **Scheduler** | `tick`, `nextFrame`, `forceFlush` |
|
|
392
|
+
|
|
393
|
+
## License
|
|
394
|
+
|
|
395
|
+
MIT
|