what-core 0.1.1 → 0.3.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/a11y.js +425 -0
- package/dist/animation.js +540 -0
- package/dist/components.js +272 -115
- package/dist/data.js +444 -0
- package/dist/dom.js +702 -427
- package/dist/form.js +441 -0
- package/dist/h.js +191 -138
- package/dist/head.js +59 -42
- package/dist/helpers.js +125 -83
- package/dist/hooks.js +226 -124
- package/dist/index.js +2 -2
- package/dist/reactive.js +165 -108
- package/dist/scheduler.js +241 -0
- package/dist/skeleton.js +363 -0
- package/dist/store.js +114 -55
- package/dist/testing.js +367 -0
- package/dist/what.js +2 -2
- package/index.d.ts +15 -0
- package/package.json +1 -1
- package/src/animation.js +11 -2
- package/src/components.js +93 -0
- package/src/data.js +19 -9
- package/src/dom.js +181 -85
- package/src/hooks.js +22 -10
- package/src/index.js +2 -2
- package/src/reactive.js +15 -1
- package/src/store.js +24 -5
package/dist/skeleton.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
// What Framework - Skeleton Loaders
|
|
2
|
+
// Loading placeholders for content, islands, and async data
|
|
3
|
+
|
|
4
|
+
import { h } from './h.js';
|
|
5
|
+
import { signal, effect } from './reactive.js';
|
|
6
|
+
|
|
7
|
+
// --- Skeleton Base Styles ---
|
|
8
|
+
|
|
9
|
+
const skeletonStyles = `
|
|
10
|
+
.what-skeleton {
|
|
11
|
+
background: linear-gradient(
|
|
12
|
+
90deg,
|
|
13
|
+
var(--skeleton-base, #e0e0e0) 0%,
|
|
14
|
+
var(--skeleton-highlight, #f0f0f0) 50%,
|
|
15
|
+
var(--skeleton-base, #e0e0e0) 100%
|
|
16
|
+
);
|
|
17
|
+
background-size: 200% 100%;
|
|
18
|
+
animation: what-skeleton-shimmer 1.5s infinite ease-in-out;
|
|
19
|
+
border-radius: var(--skeleton-radius, 4px);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@keyframes what-skeleton-shimmer {
|
|
23
|
+
0% { background-position: 200% 0; }
|
|
24
|
+
100% { background-position: -200% 0; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.what-skeleton-pulse {
|
|
28
|
+
animation: what-skeleton-pulse 1.5s infinite ease-in-out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@keyframes what-skeleton-pulse {
|
|
32
|
+
0%, 100% { opacity: 1; }
|
|
33
|
+
50% { opacity: 0.5; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.what-skeleton-wave {
|
|
37
|
+
position: relative;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.what-skeleton-wave::after {
|
|
42
|
+
content: '';
|
|
43
|
+
position: absolute;
|
|
44
|
+
top: 0;
|
|
45
|
+
right: 0;
|
|
46
|
+
bottom: 0;
|
|
47
|
+
left: 0;
|
|
48
|
+
background: linear-gradient(
|
|
49
|
+
90deg,
|
|
50
|
+
transparent 0%,
|
|
51
|
+
rgba(255, 255, 255, 0.4) 50%,
|
|
52
|
+
transparent 100%
|
|
53
|
+
);
|
|
54
|
+
animation: what-skeleton-wave 1.5s infinite;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@keyframes what-skeleton-wave {
|
|
58
|
+
0% { transform: translateX(-100%); }
|
|
59
|
+
100% { transform: translateX(100%); }
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
// Inject styles once
|
|
64
|
+
let stylesInjected = false;
|
|
65
|
+
|
|
66
|
+
function injectStyles() {
|
|
67
|
+
if (stylesInjected || typeof document === 'undefined') return;
|
|
68
|
+
stylesInjected = true;
|
|
69
|
+
|
|
70
|
+
const style = document.createElement('style');
|
|
71
|
+
style.textContent = skeletonStyles;
|
|
72
|
+
document.head.appendChild(style);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// --- Skeleton Component ---
|
|
76
|
+
|
|
77
|
+
export function Skeleton({
|
|
78
|
+
width,
|
|
79
|
+
height,
|
|
80
|
+
variant = 'shimmer', // 'shimmer' | 'pulse' | 'wave'
|
|
81
|
+
circle = false,
|
|
82
|
+
class: className,
|
|
83
|
+
style: customStyle,
|
|
84
|
+
count = 1,
|
|
85
|
+
}) {
|
|
86
|
+
injectStyles();
|
|
87
|
+
|
|
88
|
+
const baseClass = `what-skeleton ${variant === 'pulse' ? 'what-skeleton-pulse' : ''} ${variant === 'wave' ? 'what-skeleton-wave' : ''}`;
|
|
89
|
+
const finalClass = className ? `${baseClass} ${className}` : baseClass;
|
|
90
|
+
|
|
91
|
+
const style = {
|
|
92
|
+
width: typeof width === 'number' ? `${width}px` : width,
|
|
93
|
+
height: typeof height === 'number' ? `${height}px` : height,
|
|
94
|
+
borderRadius: circle ? '50%' : undefined,
|
|
95
|
+
...customStyle,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (count === 1) {
|
|
99
|
+
return h('div', { class: finalClass, style, 'aria-hidden': 'true' });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return Array.from({ length: count }, (_, i) =>
|
|
103
|
+
h('div', {
|
|
104
|
+
key: i,
|
|
105
|
+
class: finalClass,
|
|
106
|
+
style: { ...style, marginBottom: i < count - 1 ? '8px' : undefined },
|
|
107
|
+
'aria-hidden': 'true',
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// --- Skeleton Text ---
|
|
113
|
+
|
|
114
|
+
export function SkeletonText({
|
|
115
|
+
lines = 3,
|
|
116
|
+
lastLineWidth = '60%',
|
|
117
|
+
lineHeight = 16,
|
|
118
|
+
gap = 8,
|
|
119
|
+
variant = 'shimmer',
|
|
120
|
+
}) {
|
|
121
|
+
injectStyles();
|
|
122
|
+
|
|
123
|
+
return h('div', { class: 'what-skeleton-text', 'aria-hidden': 'true' },
|
|
124
|
+
Array.from({ length: lines }, (_, i) =>
|
|
125
|
+
h('div', {
|
|
126
|
+
key: i,
|
|
127
|
+
class: `what-skeleton ${variant === 'pulse' ? 'what-skeleton-pulse' : ''}`,
|
|
128
|
+
style: {
|
|
129
|
+
height: `${lineHeight}px`,
|
|
130
|
+
width: i === lines - 1 ? lastLineWidth : '100%',
|
|
131
|
+
marginBottom: i < lines - 1 ? `${gap}px` : undefined,
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// --- Skeleton Avatar ---
|
|
139
|
+
|
|
140
|
+
export function SkeletonAvatar({
|
|
141
|
+
size = 40,
|
|
142
|
+
variant = 'shimmer',
|
|
143
|
+
}) {
|
|
144
|
+
return Skeleton({
|
|
145
|
+
width: size,
|
|
146
|
+
height: size,
|
|
147
|
+
circle: true,
|
|
148
|
+
variant,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// --- Skeleton Card ---
|
|
153
|
+
|
|
154
|
+
export function SkeletonCard({
|
|
155
|
+
imageHeight = 200,
|
|
156
|
+
lines = 3,
|
|
157
|
+
variant = 'shimmer',
|
|
158
|
+
}) {
|
|
159
|
+
injectStyles();
|
|
160
|
+
|
|
161
|
+
return h('div', { class: 'what-skeleton-card', 'aria-hidden': 'true' },
|
|
162
|
+
// Image placeholder
|
|
163
|
+
h('div', {
|
|
164
|
+
class: `what-skeleton ${variant === 'pulse' ? 'what-skeleton-pulse' : ''}`,
|
|
165
|
+
style: { height: `${imageHeight}px`, width: '100%', marginBottom: '16px' },
|
|
166
|
+
}),
|
|
167
|
+
// Title
|
|
168
|
+
h('div', {
|
|
169
|
+
class: `what-skeleton ${variant === 'pulse' ? 'what-skeleton-pulse' : ''}`,
|
|
170
|
+
style: { height: '24px', width: '70%', marginBottom: '12px' },
|
|
171
|
+
}),
|
|
172
|
+
// Text lines
|
|
173
|
+
SkeletonText({ lines, variant })
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// --- Skeleton Table ---
|
|
178
|
+
|
|
179
|
+
export function SkeletonTable({
|
|
180
|
+
rows = 5,
|
|
181
|
+
columns = 4,
|
|
182
|
+
variant = 'shimmer',
|
|
183
|
+
}) {
|
|
184
|
+
injectStyles();
|
|
185
|
+
|
|
186
|
+
return h('div', { class: 'what-skeleton-table', 'aria-hidden': 'true' },
|
|
187
|
+
// Header
|
|
188
|
+
h('div', { style: { display: 'flex', gap: '16px', marginBottom: '16px' } },
|
|
189
|
+
Array.from({ length: columns }, (_, i) =>
|
|
190
|
+
h('div', {
|
|
191
|
+
key: i,
|
|
192
|
+
class: `what-skeleton ${variant === 'pulse' ? 'what-skeleton-pulse' : ''}`,
|
|
193
|
+
style: { height: '20px', flex: 1 },
|
|
194
|
+
})
|
|
195
|
+
)
|
|
196
|
+
),
|
|
197
|
+
// Rows
|
|
198
|
+
Array.from({ length: rows }, (_, rowIndex) =>
|
|
199
|
+
h('div', {
|
|
200
|
+
key: rowIndex,
|
|
201
|
+
style: {
|
|
202
|
+
display: 'flex',
|
|
203
|
+
gap: '16px',
|
|
204
|
+
marginBottom: rowIndex < rows - 1 ? '12px' : undefined,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
Array.from({ length: columns }, (_, colIndex) =>
|
|
208
|
+
h('div', {
|
|
209
|
+
key: colIndex,
|
|
210
|
+
class: `what-skeleton ${variant === 'pulse' ? 'what-skeleton-pulse' : ''}`,
|
|
211
|
+
style: { height: '16px', flex: 1 },
|
|
212
|
+
})
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// --- Island Skeleton ---
|
|
220
|
+
// Specific skeleton for island placeholders
|
|
221
|
+
|
|
222
|
+
export function IslandSkeleton({
|
|
223
|
+
type = 'default', // 'default' | 'card' | 'text' | 'custom'
|
|
224
|
+
height,
|
|
225
|
+
children,
|
|
226
|
+
}) {
|
|
227
|
+
injectStyles();
|
|
228
|
+
|
|
229
|
+
if (type === 'card') {
|
|
230
|
+
return SkeletonCard({});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (type === 'text') {
|
|
234
|
+
return SkeletonText({});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (children) {
|
|
238
|
+
return children;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return h('div', {
|
|
242
|
+
class: 'what-skeleton what-island-skeleton',
|
|
243
|
+
style: {
|
|
244
|
+
height: typeof height === 'number' ? `${height}px` : height || '100px',
|
|
245
|
+
width: '100%',
|
|
246
|
+
},
|
|
247
|
+
'aria-hidden': 'true',
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// --- useSkeleton Hook ---
|
|
252
|
+
// Show skeleton while loading data
|
|
253
|
+
|
|
254
|
+
export function useSkeleton(asyncFn, deps = []) {
|
|
255
|
+
const isLoading = signal(true);
|
|
256
|
+
const data = signal(null);
|
|
257
|
+
const error = signal(null);
|
|
258
|
+
|
|
259
|
+
effect(() => {
|
|
260
|
+
isLoading.set(true);
|
|
261
|
+
error.set(null);
|
|
262
|
+
|
|
263
|
+
Promise.resolve(asyncFn())
|
|
264
|
+
.then(result => {
|
|
265
|
+
data.set(result);
|
|
266
|
+
isLoading.set(false);
|
|
267
|
+
})
|
|
268
|
+
.catch(err => {
|
|
269
|
+
error.set(err);
|
|
270
|
+
isLoading.set(false);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
isLoading: () => isLoading(),
|
|
276
|
+
data: () => data(),
|
|
277
|
+
error: () => error(),
|
|
278
|
+
Skeleton: (props) => isLoading() ? Skeleton(props) : null,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// --- Placeholder ---
|
|
283
|
+
// Generic placeholder with optional shimmer
|
|
284
|
+
|
|
285
|
+
export function Placeholder({
|
|
286
|
+
width = '100%',
|
|
287
|
+
height = 100,
|
|
288
|
+
label = 'Loading...',
|
|
289
|
+
showLabel = false,
|
|
290
|
+
variant = 'shimmer',
|
|
291
|
+
}) {
|
|
292
|
+
injectStyles();
|
|
293
|
+
|
|
294
|
+
return h('div', {
|
|
295
|
+
class: `what-skeleton ${variant === 'pulse' ? 'what-skeleton-pulse' : ''}`,
|
|
296
|
+
style: {
|
|
297
|
+
width: typeof width === 'number' ? `${width}px` : width,
|
|
298
|
+
height: typeof height === 'number' ? `${height}px` : height,
|
|
299
|
+
display: 'flex',
|
|
300
|
+
alignItems: 'center',
|
|
301
|
+
justifyContent: 'center',
|
|
302
|
+
},
|
|
303
|
+
'aria-label': label,
|
|
304
|
+
role: 'status',
|
|
305
|
+
},
|
|
306
|
+
showLabel && h('span', {
|
|
307
|
+
style: {
|
|
308
|
+
color: 'var(--skeleton-text, #999)',
|
|
309
|
+
fontSize: '14px',
|
|
310
|
+
},
|
|
311
|
+
}, label)
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// --- Loading Dots ---
|
|
316
|
+
|
|
317
|
+
export function LoadingDots({ size = 8, color = '#666' }) {
|
|
318
|
+
injectStyles();
|
|
319
|
+
|
|
320
|
+
const dotStyle = {
|
|
321
|
+
width: `${size}px`,
|
|
322
|
+
height: `${size}px`,
|
|
323
|
+
borderRadius: '50%',
|
|
324
|
+
backgroundColor: color,
|
|
325
|
+
animation: 'what-skeleton-pulse 1s infinite ease-in-out',
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
return h('div', {
|
|
329
|
+
class: 'what-loading-dots',
|
|
330
|
+
style: { display: 'flex', gap: `${size / 2}px` },
|
|
331
|
+
'aria-label': 'Loading',
|
|
332
|
+
role: 'status',
|
|
333
|
+
},
|
|
334
|
+
h('div', { style: { ...dotStyle, animationDelay: '0s' } }),
|
|
335
|
+
h('div', { style: { ...dotStyle, animationDelay: '0.2s' } }),
|
|
336
|
+
h('div', { style: { ...dotStyle, animationDelay: '0.4s' } })
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// --- Spinner ---
|
|
341
|
+
|
|
342
|
+
export function Spinner({ size = 24, color = '#666', strokeWidth = 2 }) {
|
|
343
|
+
return h('svg', {
|
|
344
|
+
width: size,
|
|
345
|
+
height: size,
|
|
346
|
+
viewBox: '0 0 24 24',
|
|
347
|
+
style: { animation: 'spin 1s linear infinite' },
|
|
348
|
+
'aria-label': 'Loading',
|
|
349
|
+
role: 'status',
|
|
350
|
+
},
|
|
351
|
+
h('style', null, '@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }'),
|
|
352
|
+
h('circle', {
|
|
353
|
+
cx: 12,
|
|
354
|
+
cy: 12,
|
|
355
|
+
r: 10,
|
|
356
|
+
stroke: color,
|
|
357
|
+
strokeWidth,
|
|
358
|
+
fill: 'none',
|
|
359
|
+
strokeDasharray: '31.4 31.4',
|
|
360
|
+
strokeLinecap: 'round',
|
|
361
|
+
})
|
|
362
|
+
);
|
|
363
|
+
}
|
package/dist/store.js
CHANGED
|
@@ -1,59 +1,118 @@
|
|
|
1
|
+
// What Framework - Store
|
|
2
|
+
// Lightweight global state management. Signal-based, type-safe, ergonomic.
|
|
3
|
+
// Like Zustand meets signals — define a store, use it anywhere.
|
|
4
|
+
|
|
1
5
|
import { signal, computed, batch } from './reactive.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const proxy = new Proxy({}, {
|
|
18
|
-
get(_, prop) {
|
|
19
|
-
if (signals[prop]) return signals[prop]();
|
|
20
|
-
if (computeds[prop]) return computeds[prop]();
|
|
21
|
-
return undefined;
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
computeds[key] = computed(() => fn(proxy));
|
|
25
|
-
}
|
|
26
|
-
for (const [key, fn] of Object.entries(actions)) {
|
|
27
|
-
actions[key] = (...args) => {
|
|
28
|
-
batch(() => {
|
|
29
|
-
const proxy = new Proxy({}, {
|
|
30
|
-
get(_, prop) {
|
|
31
|
-
if (signals[prop]) return signals[prop].peek();
|
|
32
|
-
return undefined;
|
|
33
|
-
},
|
|
34
|
-
set(_, prop, val) {
|
|
35
|
-
if (signals[prop]) signals[prop].set(val);
|
|
36
|
-
return true;
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
fn.apply(proxy, args);
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
return function useStore() {
|
|
44
|
-
const result = {};
|
|
45
|
-
for (const [key, s] of Object.entries(signals)) {
|
|
46
|
-
Object.defineProperty(result, key, { get: () => s(), enumerable: true });
|
|
6
|
+
|
|
7
|
+
// --- storeComputed ---
|
|
8
|
+
// Marker wrapper to explicitly tag a function as a computed in createStore.
|
|
9
|
+
// Without this, createStore can't distinguish computed(state => ...) from action(item => ...).
|
|
10
|
+
//
|
|
11
|
+
// Usage:
|
|
12
|
+
// const useCounter = createStore({
|
|
13
|
+
// count: 0,
|
|
14
|
+
// doubled: storeComputed(state => state.count * 2),
|
|
15
|
+
// addItem(item) { /* this is an action */ },
|
|
16
|
+
// });
|
|
17
|
+
|
|
18
|
+
export function storeComputed(fn) {
|
|
19
|
+
fn._storeComputed = true;
|
|
20
|
+
return fn;
|
|
47
21
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
22
|
+
|
|
23
|
+
// --- createStore ---
|
|
24
|
+
// Creates a reactive store with actions. Each key becomes a signal.
|
|
25
|
+
//
|
|
26
|
+
// Usage:
|
|
27
|
+
// const useCounter = createStore({
|
|
28
|
+
// count: 0,
|
|
29
|
+
// doubled: storeComputed(state => state.count * 2), // computed
|
|
30
|
+
// increment() { this.count++; }, // action
|
|
31
|
+
// decrement() { this.count--; },
|
|
32
|
+
// addItem(item) { this.items.push(item); }, // action (not confused with computed)
|
|
33
|
+
// });
|
|
34
|
+
//
|
|
35
|
+
// function Counter() {
|
|
36
|
+
// const { count, doubled, increment } = useCounter();
|
|
37
|
+
// return h('div', null, count, ' / ', doubled, h('button', { onClick: increment }, '+'));
|
|
38
|
+
// }
|
|
39
|
+
|
|
40
|
+
export function createStore(definition) {
|
|
41
|
+
const signals = {};
|
|
42
|
+
const computeds = {};
|
|
43
|
+
const actions = {};
|
|
44
|
+
const state = {};
|
|
45
|
+
|
|
46
|
+
// Separate state, computeds, and actions
|
|
47
|
+
// Use explicit _storeComputed marker instead of function.length heuristic
|
|
48
|
+
for (const [key, value] of Object.entries(definition)) {
|
|
49
|
+
if (typeof value === 'function' && value._storeComputed) {
|
|
50
|
+
// Computed: explicitly marked with storeComputed()
|
|
51
|
+
computeds[key] = value;
|
|
52
|
+
} else if (typeof value === 'function') {
|
|
53
|
+
// Action: any other function
|
|
54
|
+
actions[key] = value;
|
|
55
|
+
} else {
|
|
56
|
+
// State: initial value
|
|
57
|
+
signals[key] = signal(value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Build computed signals
|
|
62
|
+
for (const [key, fn] of Object.entries(computeds)) {
|
|
63
|
+
const proxy = new Proxy({}, {
|
|
64
|
+
get(_, prop) {
|
|
65
|
+
if (signals[prop]) return signals[prop]();
|
|
66
|
+
if (computeds[prop]) return computeds[prop]();
|
|
67
|
+
return undefined;
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
computeds[key] = computed(() => fn(proxy));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Build action functions bound to signals
|
|
74
|
+
for (const [key, fn] of Object.entries(actions)) {
|
|
75
|
+
actions[key] = (...args) => {
|
|
76
|
+
batch(() => {
|
|
77
|
+
const proxy = new Proxy({}, {
|
|
78
|
+
get(_, prop) {
|
|
79
|
+
if (signals[prop]) return signals[prop].peek();
|
|
80
|
+
if (computeds[prop]) return computeds[prop].peek();
|
|
81
|
+
return undefined;
|
|
82
|
+
},
|
|
83
|
+
set(_, prop, val) {
|
|
84
|
+
if (signals[prop]) signals[prop].set(val);
|
|
85
|
+
return true;
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
fn.apply(proxy, args);
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Return a hook-like function
|
|
94
|
+
return function useStore() {
|
|
95
|
+
const result = {};
|
|
96
|
+
for (const [key, s] of Object.entries(signals)) {
|
|
97
|
+
Object.defineProperty(result, key, { get: () => s(), enumerable: true });
|
|
98
|
+
}
|
|
99
|
+
for (const [key, c] of Object.entries(computeds)) {
|
|
100
|
+
Object.defineProperty(result, key, { get: () => c(), enumerable: true });
|
|
101
|
+
}
|
|
102
|
+
for (const [key, fn] of Object.entries(actions)) {
|
|
103
|
+
result[key] = fn;
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
};
|
|
56
107
|
}
|
|
108
|
+
|
|
109
|
+
// --- Simple atom ---
|
|
110
|
+
// Even simpler: a single reactive value accessible globally.
|
|
111
|
+
//
|
|
112
|
+
// const count = atom(0);
|
|
113
|
+
// count(); // read
|
|
114
|
+
// count.set(5); // write
|
|
115
|
+
|
|
57
116
|
export function atom(initial) {
|
|
58
|
-
return signal(initial);
|
|
59
|
-
}
|
|
117
|
+
return signal(initial);
|
|
118
|
+
}
|