@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.
Files changed (102) hide show
  1. package/dist/constants.cjs +13 -0
  2. package/dist/constants.cjs.map +1 -0
  3. package/dist/constants.mjs +13 -0
  4. package/dist/constants.mjs.map +1 -0
  5. package/dist/context/context.cjs +51 -0
  6. package/dist/context/context.cjs.map +1 -0
  7. package/dist/context/context.d.cts +25 -0
  8. package/dist/context/context.d.mts +25 -0
  9. package/dist/context/context.mjs +50 -0
  10. package/dist/context/context.mjs.map +1 -0
  11. package/dist/fetch/resource.cjs +89 -0
  12. package/dist/fetch/resource.cjs.map +1 -0
  13. package/dist/fetch/resource.d.cts +14 -0
  14. package/dist/fetch/resource.d.mts +14 -0
  15. package/dist/fetch/resource.mjs +88 -0
  16. package/dist/fetch/resource.mjs.map +1 -0
  17. package/dist/index.cjs +58 -0
  18. package/dist/index.d.cts +15 -0
  19. package/dist/index.d.mts +15 -0
  20. package/dist/index.mjs +14 -0
  21. package/dist/jsx/element.cjs +201 -0
  22. package/dist/jsx/element.cjs.map +1 -0
  23. package/dist/jsx/element.d.cts +48 -0
  24. package/dist/jsx/element.d.mts +48 -0
  25. package/dist/jsx/element.mjs +199 -0
  26. package/dist/jsx/element.mjs.map +1 -0
  27. package/dist/jsx-dev-runtime.cjs +40 -0
  28. package/dist/jsx-dev-runtime.cjs.map +1 -0
  29. package/dist/jsx-dev-runtime.d.cts +21 -0
  30. package/dist/jsx-dev-runtime.d.mts +21 -0
  31. package/dist/jsx-dev-runtime.mjs +36 -0
  32. package/dist/jsx-dev-runtime.mjs.map +1 -0
  33. package/dist/jsx-runtime.cjs +5 -0
  34. package/dist/jsx-runtime.d.cts +3 -0
  35. package/dist/jsx-runtime.d.mts +3 -0
  36. package/dist/jsx-runtime.mjs +2 -0
  37. package/dist/jsx.d.cts +942 -0
  38. package/dist/jsx.d.mts +942 -0
  39. package/dist/lifecycle/hooks.cjs +56 -0
  40. package/dist/lifecycle/hooks.cjs.map +1 -0
  41. package/dist/lifecycle/hooks.d.cts +37 -0
  42. package/dist/lifecycle/hooks.d.mts +37 -0
  43. package/dist/lifecycle/hooks.mjs +54 -0
  44. package/dist/lifecycle/hooks.mjs.map +1 -0
  45. package/dist/render/engine.cjs +52 -0
  46. package/dist/render/engine.cjs.map +1 -0
  47. package/dist/render/engine.d.cts +31 -0
  48. package/dist/render/engine.d.mts +31 -0
  49. package/dist/render/engine.mjs +51 -0
  50. package/dist/render/engine.mjs.map +1 -0
  51. package/dist/render/flow.cjs +286 -0
  52. package/dist/render/flow.cjs.map +1 -0
  53. package/dist/render/flow.d.cts +64 -0
  54. package/dist/render/flow.d.mts +64 -0
  55. package/dist/render/flow.mjs +279 -0
  56. package/dist/render/flow.mjs.map +1 -0
  57. package/dist/scheduler/scheduler.cjs +61 -0
  58. package/dist/scheduler/scheduler.cjs.map +1 -0
  59. package/dist/scheduler/scheduler.d.cts +31 -0
  60. package/dist/scheduler/scheduler.d.mts +31 -0
  61. package/dist/scheduler/scheduler.mjs +59 -0
  62. package/dist/scheduler/scheduler.mjs.map +1 -0
  63. package/dist/signal/signal.cjs +387 -0
  64. package/dist/signal/signal.cjs.map +1 -0
  65. package/dist/signal/signal.d.cts +44 -0
  66. package/dist/signal/signal.d.mts +44 -0
  67. package/dist/signal/signal.mjs +370 -0
  68. package/dist/signal/signal.mjs.map +1 -0
  69. package/dist/store/index.cjs +1 -0
  70. package/dist/store/index.mjs +2 -0
  71. package/dist/store/store.cjs +94 -0
  72. package/dist/store/store.cjs.map +1 -0
  73. package/dist/store/store.d.cts +22 -0
  74. package/dist/store/store.d.mts +22 -0
  75. package/dist/store/store.mjs +91 -0
  76. package/dist/store/store.mjs.map +1 -0
  77. package/dist/style/computed.cjs +65 -0
  78. package/dist/style/computed.cjs.map +1 -0
  79. package/dist/style/computed.d.cts +18 -0
  80. package/dist/style/computed.d.mts +18 -0
  81. package/dist/style/computed.mjs +63 -0
  82. package/dist/style/computed.mjs.map +1 -0
  83. package/dist/text/text.cjs +74 -0
  84. package/dist/text/text.cjs.map +1 -0
  85. package/dist/text/text.d.cts +31 -0
  86. package/dist/text/text.d.mts +31 -0
  87. package/dist/text/text.mjs +72 -0
  88. package/dist/text/text.mjs.map +1 -0
  89. package/dist/types.d.cts +185 -0
  90. package/dist/types.d.mts +185 -0
  91. package/dist/utils/index.cjs +34 -0
  92. package/dist/utils/index.cjs.map +1 -0
  93. package/dist/utils/index.mjs +33 -0
  94. package/dist/utils/index.mjs.map +1 -0
  95. package/dist/watch/watcher.cjs +71 -0
  96. package/dist/watch/watcher.cjs.map +1 -0
  97. package/dist/watch/watcher.d.cts +17 -0
  98. package/dist/watch/watcher.d.mts +17 -0
  99. package/dist/watch/watcher.mjs +70 -0
  100. package/dist/watch/watcher.mjs.map +1 -0
  101. package/package.json +34 -0
  102. 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