what-core 0.1.1 → 0.2.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 +531 -0
- package/dist/components.js +272 -115
- package/dist/data.js +434 -0
- package/dist/dom.js +635 -424
- 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 +224 -134
- package/dist/index.js +2 -2
- package/dist/reactive.js +150 -107
- package/dist/scheduler.js +241 -0
- package/dist/skeleton.js +363 -0
- package/dist/store.js +113 -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/components.js +93 -0
- package/src/dom.js +47 -15
- package/src/index.js +2 -2
- package/src/store.js +23 -5
package/dist/helpers.js
CHANGED
|
@@ -1,98 +1,140 @@
|
|
|
1
|
+
// What Framework - Helpers & Utilities
|
|
2
|
+
// Commonly needed patterns, zero overhead.
|
|
3
|
+
|
|
1
4
|
import { signal, effect, computed, batch } from './reactive.js';
|
|
5
|
+
|
|
6
|
+
// --- show(condition, vnode) ---
|
|
7
|
+
// Conditional rendering. More readable than ternary.
|
|
2
8
|
export function show(condition, vnode, fallback = null) {
|
|
3
|
-
return condition ? vnode : fallback;
|
|
9
|
+
return condition ? vnode : fallback;
|
|
4
10
|
}
|
|
11
|
+
|
|
12
|
+
// --- each(list, fn) ---
|
|
13
|
+
// Keyed list rendering. Optimized for reconciliation.
|
|
5
14
|
export function each(list, fn, keyFn) {
|
|
6
|
-
if (!list || list.length === 0) return [];
|
|
7
|
-
return list.map((item, index) => {
|
|
8
|
-
const vnode = fn(item, index);
|
|
9
|
-
if (keyFn && vnode && typeof vnode === 'object') {
|
|
10
|
-
vnode.key = keyFn(item, index);
|
|
11
|
-
}
|
|
12
|
-
return vnode;
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
+
if (!list || list.length === 0) return [];
|
|
16
|
+
return list.map((item, index) => {
|
|
17
|
+
const vnode = fn(item, index);
|
|
18
|
+
if (keyFn && vnode && typeof vnode === 'object') {
|
|
19
|
+
vnode.key = keyFn(item, index);
|
|
20
|
+
}
|
|
21
|
+
return vnode;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// --- cls(...args) ---
|
|
26
|
+
// Conditional class names. Like clsx but tiny.
|
|
27
|
+
// cls('base', condition && 'active', { hidden: isHidden, bold: isBold })
|
|
15
28
|
export function cls(...args) {
|
|
16
|
-
const classes = [];
|
|
17
|
-
for (const arg of args) {
|
|
18
|
-
if (!arg) continue;
|
|
19
|
-
if (typeof arg === 'string') {
|
|
20
|
-
classes.push(arg);
|
|
21
|
-
} else if (typeof arg === 'object') {
|
|
22
|
-
for (const [key, val] of Object.entries(arg)) {
|
|
23
|
-
if (val) classes.push(key);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return classes.join(' ');
|
|
28
|
-
}
|
|
29
|
+
const classes = [];
|
|
30
|
+
for (const arg of args) {
|
|
31
|
+
if (!arg) continue;
|
|
32
|
+
if (typeof arg === 'string') {
|
|
33
|
+
classes.push(arg);
|
|
34
|
+
} else if (typeof arg === 'object') {
|
|
35
|
+
for (const [key, val] of Object.entries(arg)) {
|
|
36
|
+
if (val) classes.push(key);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return classes.join(' ');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// --- style(obj) ---
|
|
44
|
+
// Convert a style object to a CSS string for SSR.
|
|
29
45
|
export function style(obj) {
|
|
30
|
-
if (typeof obj === 'string') return obj;
|
|
31
|
-
return Object.entries(obj)
|
|
32
|
-
.filter(([, v]) => v != null && v !== '')
|
|
33
|
-
.map(([k, v]) => `${camelToKebab(k)}:${v}`)
|
|
34
|
-
.join(';');
|
|
46
|
+
if (typeof obj === 'string') return obj;
|
|
47
|
+
return Object.entries(obj)
|
|
48
|
+
.filter(([, v]) => v != null && v !== '')
|
|
49
|
+
.map(([k, v]) => `${camelToKebab(k)}:${v}`)
|
|
50
|
+
.join(';');
|
|
35
51
|
}
|
|
52
|
+
|
|
36
53
|
function camelToKebab(str) {
|
|
37
|
-
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
54
|
+
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
38
55
|
}
|
|
56
|
+
|
|
57
|
+
// --- debounce ---
|
|
58
|
+
// Debounced signal updates.
|
|
39
59
|
export function debounce(fn, ms) {
|
|
40
|
-
let timer;
|
|
41
|
-
return (...args) => {
|
|
42
|
-
clearTimeout(timer);
|
|
43
|
-
timer = setTimeout(() => fn(...args), ms);
|
|
44
|
-
};
|
|
45
|
-
}
|
|
60
|
+
let timer;
|
|
61
|
+
return (...args) => {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
timer = setTimeout(() => fn(...args), ms);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// --- throttle ---
|
|
46
68
|
export function throttle(fn, ms) {
|
|
47
|
-
let last = 0;
|
|
48
|
-
return (...args) => {
|
|
49
|
-
const now = Date.now();
|
|
50
|
-
if (now - last >= ms) {
|
|
51
|
-
last = now;
|
|
52
|
-
fn(...args);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
69
|
+
let last = 0;
|
|
70
|
+
return (...args) => {
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
if (now - last >= ms) {
|
|
73
|
+
last = now;
|
|
74
|
+
fn(...args);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// --- useMediaQuery ---
|
|
80
|
+
// Reactive media query. Returns a signal.
|
|
56
81
|
export function useMediaQuery(query) {
|
|
57
|
-
if (typeof window === 'undefined') return signal(false);
|
|
58
|
-
const mq = window.matchMedia(query);
|
|
59
|
-
const s = signal(mq.matches);
|
|
60
|
-
mq.addEventListener('change', (e) => s.set(e.matches));
|
|
61
|
-
return s;
|
|
62
|
-
}
|
|
82
|
+
if (typeof window === 'undefined') return signal(false);
|
|
83
|
+
const mq = window.matchMedia(query);
|
|
84
|
+
const s = signal(mq.matches);
|
|
85
|
+
mq.addEventListener('change', (e) => s.set(e.matches));
|
|
86
|
+
return s;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- useLocalStorage ---
|
|
90
|
+
// Signal synced with localStorage.
|
|
63
91
|
export function useLocalStorage(key, initial) {
|
|
64
|
-
let stored;
|
|
65
|
-
try {
|
|
66
|
-
const raw = localStorage.getItem(key);
|
|
67
|
-
stored = raw !== null ? JSON.parse(raw) : initial;
|
|
68
|
-
} catch {
|
|
69
|
-
stored = initial;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
localStorage
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
92
|
+
let stored;
|
|
93
|
+
try {
|
|
94
|
+
const raw = localStorage.getItem(key);
|
|
95
|
+
stored = raw !== null ? JSON.parse(raw) : initial;
|
|
96
|
+
} catch {
|
|
97
|
+
stored = initial;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const s = signal(stored);
|
|
101
|
+
|
|
102
|
+
// Sync to localStorage on changes
|
|
103
|
+
effect(() => {
|
|
104
|
+
try {
|
|
105
|
+
localStorage.setItem(key, JSON.stringify(s()));
|
|
106
|
+
} catch { /* quota exceeded, etc */ }
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Listen for changes from other tabs
|
|
110
|
+
if (typeof window !== 'undefined') {
|
|
111
|
+
window.addEventListener('storage', (e) => {
|
|
112
|
+
if (e.key === key && e.newValue !== null) {
|
|
113
|
+
try { s.set(JSON.parse(e.newValue)); } catch {}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return s;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- portal ---
|
|
122
|
+
// Render children into a different DOM container.
|
|
86
123
|
export function Portal({ target, children }) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
124
|
+
// In SSR, just return null (portals are client-only)
|
|
125
|
+
if (typeof document === 'undefined') return null;
|
|
126
|
+
const container = typeof target === 'string'
|
|
127
|
+
? document.querySelector(target)
|
|
128
|
+
: target;
|
|
129
|
+
if (!container) return null;
|
|
130
|
+
// The DOM reconciler will mount children here
|
|
131
|
+
return { tag: '__portal', props: { container }, children: Array.isArray(children) ? children : [children], _vnode: true };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// --- Transition helper ---
|
|
135
|
+
// Animate elements in/out. Returns props to spread on the element.
|
|
94
136
|
export function transition(name, active) {
|
|
95
|
-
return {
|
|
96
|
-
class: active ? `${name}-enter ${name}-enter-active` : `${name}-leave ${name}-leave-active`,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
137
|
+
return {
|
|
138
|
+
class: active ? `${name}-enter ${name}-enter-active` : `${name}-leave ${name}-leave-active`,
|
|
139
|
+
};
|
|
140
|
+
}
|
package/dist/hooks.js
CHANGED
|
@@ -1,156 +1,246 @@
|
|
|
1
|
+
// What Framework - Hooks
|
|
2
|
+
// React-familiar hooks backed by signals. Zero overhead when deps don't change.
|
|
3
|
+
|
|
1
4
|
import { signal, computed, effect, batch, untrack } from './reactive.js';
|
|
2
5
|
import { getCurrentComponent } from './dom.js';
|
|
6
|
+
|
|
3
7
|
function getCtx() {
|
|
4
|
-
const ctx = getCurrentComponent();
|
|
5
|
-
if (!ctx) throw new Error('Hooks must be called inside a component');
|
|
6
|
-
return ctx;
|
|
8
|
+
const ctx = getCurrentComponent();
|
|
9
|
+
if (!ctx) throw new Error('Hooks must be called inside a component');
|
|
10
|
+
return ctx;
|
|
7
11
|
}
|
|
12
|
+
|
|
8
13
|
function getHook(ctx) {
|
|
9
|
-
const index = ctx.hookIndex++;
|
|
10
|
-
return { index, exists: index < ctx.hooks.length };
|
|
14
|
+
const index = ctx.hookIndex++;
|
|
15
|
+
return { index, exists: index < ctx.hooks.length };
|
|
11
16
|
}
|
|
17
|
+
|
|
18
|
+
// --- useState ---
|
|
19
|
+
// Returns [value, setter]. Setter triggers re-render of this component only.
|
|
20
|
+
|
|
12
21
|
export function useState(initial) {
|
|
13
|
-
const ctx = getCtx();
|
|
14
|
-
const { index, exists } = getHook(ctx);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
const ctx = getCtx();
|
|
23
|
+
const { index, exists } = getHook(ctx);
|
|
24
|
+
|
|
25
|
+
if (!exists) {
|
|
26
|
+
const s = signal(typeof initial === 'function' ? initial() : initial);
|
|
27
|
+
ctx.hooks[index] = s;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const s = ctx.hooks[index];
|
|
31
|
+
return [s(), s.set];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- useSignal ---
|
|
35
|
+
// Returns the raw signal. More powerful: read with sig(), write with sig.set(v).
|
|
36
|
+
// Avoids array destructuring overhead.
|
|
37
|
+
|
|
22
38
|
export function useSignal(initial) {
|
|
23
|
-
const ctx = getCtx();
|
|
24
|
-
const { index, exists } = getHook(ctx);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
const ctx = getCtx();
|
|
40
|
+
const { index, exists } = getHook(ctx);
|
|
41
|
+
|
|
42
|
+
if (!exists) {
|
|
43
|
+
ctx.hooks[index] = signal(typeof initial === 'function' ? initial() : initial);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return ctx.hooks[index];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// --- useComputed ---
|
|
50
|
+
// Derived value. Only recomputes when signal deps change.
|
|
51
|
+
|
|
30
52
|
export function useComputed(fn) {
|
|
31
|
-
const ctx = getCtx();
|
|
32
|
-
const { index, exists } = getHook(ctx);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
const ctx = getCtx();
|
|
54
|
+
const { index, exists } = getHook(ctx);
|
|
55
|
+
|
|
56
|
+
if (!exists) {
|
|
57
|
+
ctx.hooks[index] = computed(fn);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return ctx.hooks[index];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// --- useEffect ---
|
|
64
|
+
// Side effect that runs after render. Cleanup function returned by fn is called
|
|
65
|
+
// before re-running and on unmount.
|
|
66
|
+
|
|
38
67
|
export function useEffect(fn, deps) {
|
|
39
|
-
const ctx = getCtx();
|
|
40
|
-
const { index, exists } = getHook(ctx);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (hook.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
const ctx = getCtx();
|
|
69
|
+
const { index, exists } = getHook(ctx);
|
|
70
|
+
|
|
71
|
+
if (!exists) {
|
|
72
|
+
ctx.hooks[index] = { deps: undefined, cleanup: null };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const hook = ctx.hooks[index];
|
|
76
|
+
|
|
77
|
+
if (depsChanged(hook.deps, deps)) {
|
|
78
|
+
// Schedule after current render
|
|
79
|
+
queueMicrotask(() => {
|
|
80
|
+
if (ctx.disposed) return;
|
|
81
|
+
if (hook.cleanup) hook.cleanup();
|
|
82
|
+
hook.cleanup = fn() || null;
|
|
83
|
+
});
|
|
84
|
+
hook.deps = deps;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// --- useMemo ---
|
|
89
|
+
// Memoized value. Only recomputes when deps change.
|
|
90
|
+
|
|
54
91
|
export function useMemo(fn, deps) {
|
|
55
|
-
const ctx = getCtx();
|
|
56
|
-
const { index, exists } = getHook(ctx);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
hook
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
92
|
+
const ctx = getCtx();
|
|
93
|
+
const { index, exists } = getHook(ctx);
|
|
94
|
+
|
|
95
|
+
if (!exists) {
|
|
96
|
+
ctx.hooks[index] = { value: undefined, deps: undefined };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const hook = ctx.hooks[index];
|
|
100
|
+
|
|
101
|
+
if (depsChanged(hook.deps, deps)) {
|
|
102
|
+
hook.value = fn();
|
|
103
|
+
hook.deps = deps;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return hook.value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// --- useCallback ---
|
|
110
|
+
// Memoized callback. Identity-stable when deps don't change.
|
|
111
|
+
|
|
67
112
|
export function useCallback(fn, deps) {
|
|
68
|
-
return useMemo(() => fn, deps);
|
|
113
|
+
return useMemo(() => fn, deps);
|
|
69
114
|
}
|
|
115
|
+
|
|
116
|
+
// --- useRef ---
|
|
117
|
+
// Mutable ref object. Does NOT trigger re-renders.
|
|
118
|
+
|
|
70
119
|
export function useRef(initial) {
|
|
71
|
-
const ctx = getCtx();
|
|
72
|
-
const { index, exists } = getHook(ctx);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
120
|
+
const ctx = getCtx();
|
|
121
|
+
const { index, exists } = getHook(ctx);
|
|
122
|
+
|
|
123
|
+
if (!exists) {
|
|
124
|
+
ctx.hooks[index] = { current: initial };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return ctx.hooks[index];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- useContext ---
|
|
131
|
+
// Read from a context created by createContext().
|
|
132
|
+
|
|
78
133
|
export function useContext(context) {
|
|
79
|
-
return context._value;
|
|
134
|
+
return context._value;
|
|
80
135
|
}
|
|
136
|
+
|
|
137
|
+
// --- createContext ---
|
|
138
|
+
// Simple context: set a default, override with Provider component.
|
|
139
|
+
|
|
81
140
|
export function createContext(defaultValue) {
|
|
82
|
-
const ctx = {
|
|
83
|
-
_value: defaultValue,
|
|
84
|
-
Provider: ({ value, children }) => {
|
|
85
|
-
ctx._value = value;
|
|
86
|
-
return children;
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
return ctx;
|
|
90
|
-
}
|
|
141
|
+
const ctx = {
|
|
142
|
+
_value: defaultValue,
|
|
143
|
+
Provider: ({ value, children }) => {
|
|
144
|
+
ctx._value = value;
|
|
145
|
+
return children;
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
return ctx;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- useReducer ---
|
|
152
|
+
// State management with a reducer function (like React).
|
|
153
|
+
|
|
91
154
|
export function useReducer(reducer, initialState, init) {
|
|
92
|
-
const ctx = getCtx();
|
|
93
|
-
const { index, exists } = getHook(ctx);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
155
|
+
const ctx = getCtx();
|
|
156
|
+
const { index, exists } = getHook(ctx);
|
|
157
|
+
|
|
158
|
+
if (!exists) {
|
|
159
|
+
const initial = init ? init(initialState) : initialState;
|
|
160
|
+
const s = signal(initial);
|
|
161
|
+
const dispatch = (action) => {
|
|
162
|
+
s.set(prev => reducer(prev, action));
|
|
163
|
+
};
|
|
164
|
+
ctx.hooks[index] = { signal: s, dispatch };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const hook = ctx.hooks[index];
|
|
168
|
+
return [hook.signal(), hook.dispatch];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// --- onMount ---
|
|
172
|
+
// Run callback once when component mounts. SolidJS-style lifecycle.
|
|
173
|
+
|
|
105
174
|
export function onMount(fn) {
|
|
106
|
-
const ctx = getCtx();
|
|
107
|
-
if (!ctx._mounted) {
|
|
108
|
-
ctx._mountCallbacks = ctx._mountCallbacks || [];
|
|
109
|
-
ctx._mountCallbacks.push(fn);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
175
|
+
const ctx = getCtx();
|
|
176
|
+
if (!ctx._mounted) {
|
|
177
|
+
ctx._mountCallbacks = ctx._mountCallbacks || [];
|
|
178
|
+
ctx._mountCallbacks.push(fn);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// --- onCleanup ---
|
|
183
|
+
// Register cleanup function to run when component unmounts.
|
|
184
|
+
|
|
112
185
|
export function onCleanup(fn) {
|
|
113
|
-
const ctx = getCtx();
|
|
114
|
-
ctx._cleanupCallbacks = ctx._cleanupCallbacks || [];
|
|
115
|
-
ctx._cleanupCallbacks.push(fn);
|
|
116
|
-
}
|
|
186
|
+
const ctx = getCtx();
|
|
187
|
+
ctx._cleanupCallbacks = ctx._cleanupCallbacks || [];
|
|
188
|
+
ctx._cleanupCallbacks.push(fn);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// --- createResource ---
|
|
192
|
+
// Reactive data fetching primitive (SolidJS-style).
|
|
193
|
+
// Returns [data, { loading, error, refetch, mutate }]
|
|
194
|
+
|
|
117
195
|
export function createResource(fetcher, options = {}) {
|
|
118
|
-
const data = signal(options.initialValue ?? null);
|
|
119
|
-
const loading = signal(!options.initialValue);
|
|
120
|
-
const error = signal(null);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (currentFetch ===
|
|
135
|
-
|
|
136
|
-
loading.set(false);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
196
|
+
const data = signal(options.initialValue ?? null);
|
|
197
|
+
const loading = signal(!options.initialValue);
|
|
198
|
+
const error = signal(null);
|
|
199
|
+
|
|
200
|
+
let currentFetch = null;
|
|
201
|
+
|
|
202
|
+
const refetch = async (source) => {
|
|
203
|
+
loading.set(true);
|
|
204
|
+
error.set(null);
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const fetchPromise = fetcher(source);
|
|
208
|
+
currentFetch = fetchPromise;
|
|
209
|
+
const result = await fetchPromise;
|
|
210
|
+
|
|
211
|
+
// Only update if this is still the current fetch
|
|
212
|
+
if (currentFetch === fetchPromise) {
|
|
213
|
+
data.set(result);
|
|
214
|
+
loading.set(false);
|
|
215
|
+
}
|
|
216
|
+
} catch (e) {
|
|
217
|
+
if (currentFetch === fetcher) {
|
|
218
|
+
error.set(e);
|
|
219
|
+
loading.set(false);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const mutate = (value) => {
|
|
225
|
+
data.set(typeof value === 'function' ? value(data()) : value);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// Initial fetch if no initial value
|
|
229
|
+
if (!options.initialValue) {
|
|
230
|
+
refetch(options.source);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return [data, { loading, error, refetch, mutate }];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// --- Dep comparison ---
|
|
237
|
+
|
|
148
238
|
function depsChanged(oldDeps, newDeps) {
|
|
149
|
-
if (oldDeps === undefined) return true;
|
|
150
|
-
if (!oldDeps || !newDeps) return true;
|
|
151
|
-
if (oldDeps.length !== newDeps.length) return true;
|
|
152
|
-
for (let i = 0; i < oldDeps.length; i++) {
|
|
153
|
-
if (!Object.is(oldDeps[i], newDeps[i])) return true;
|
|
154
|
-
}
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
239
|
+
if (oldDeps === undefined) return true;
|
|
240
|
+
if (!oldDeps || !newDeps) return true;
|
|
241
|
+
if (oldDeps.length !== newDeps.length) return true;
|
|
242
|
+
for (let i = 0; i < oldDeps.length; i++) {
|
|
243
|
+
if (!Object.is(oldDeps[i], newDeps[i])) return true;
|
|
244
|
+
}
|
|
245
|
+
return false;
|
|
246
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -28,10 +28,10 @@ export {
|
|
|
28
28
|
} from './hooks.js';
|
|
29
29
|
|
|
30
30
|
// Component helpers
|
|
31
|
-
export { memo, lazy, Suspense, ErrorBoundary, Show, For, Switch, Match } from './components.js';
|
|
31
|
+
export { memo, lazy, Suspense, ErrorBoundary, Show, For, Switch, Match, Island } from './components.js';
|
|
32
32
|
|
|
33
33
|
// Store
|
|
34
|
-
export { createStore, atom } from './store.js';
|
|
34
|
+
export { createStore, storeComputed, atom } from './store.js';
|
|
35
35
|
|
|
36
36
|
// Head management
|
|
37
37
|
export { Head, clearHead } from './head.js';
|