what-core 0.4.1 → 0.5.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/components.js +213 -319
- package/dist/dom.js +730 -857
- package/dist/h.js +140 -191
- package/dist/head.js +42 -59
- package/dist/helpers.js +124 -187
- package/dist/hooks.js +186 -279
- package/dist/reactive.js +244 -317
- package/dist/store.js +73 -118
- package/dist/what.js +5 -3
- package/index.d.ts +391 -152
- package/package.json +4 -3
- package/render.d.ts +11 -0
- package/src/a11y.js +52 -6
- package/src/dom.js +137 -22
- package/src/form.js +85 -54
- package/src/helpers.js +1 -12
- package/src/hooks.js +11 -0
- package/src/index.js +1 -1
- package/src/render.js +114 -51
- package/src/store.js +6 -1
package/dist/reactive.js
CHANGED
|
@@ -1,341 +1,268 @@
|
|
|
1
|
-
// What Framework - Reactive Primitives
|
|
2
|
-
// Signals + Effects: fine-grained reactivity without virtual DOM overhead
|
|
3
|
-
|
|
4
|
-
// Dev-mode flag — build tools can dead-code-eliminate when false
|
|
5
1
|
export const __DEV__ = typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production' || true;
|
|
6
|
-
|
|
7
2
|
let currentEffect = null;
|
|
8
3
|
let currentRoot = null;
|
|
9
4
|
let batchDepth = 0;
|
|
10
5
|
let pendingEffects = [];
|
|
11
|
-
|
|
12
|
-
// --- Signal ---
|
|
13
|
-
// A reactive value. Reading inside an effect auto-tracks the dependency.
|
|
14
|
-
// Writing triggers only the effects that depend on this signal.
|
|
15
|
-
|
|
16
6
|
export function signal(initial) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// --- Computed ---
|
|
46
|
-
// Derived signal. Lazy: only recomputes when a dependency changes AND it's read.
|
|
47
|
-
|
|
7
|
+
let value = initial;
|
|
8
|
+
const subs = new Set();
|
|
9
|
+
function sig(...args) {
|
|
10
|
+
if (args.length === 0) {
|
|
11
|
+
if (currentEffect) {
|
|
12
|
+
subs.add(currentEffect);
|
|
13
|
+
currentEffect.deps.push(subs);
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
const nextVal = typeof args[0] === 'function' ? args[0](value) : args[0];
|
|
18
|
+
if (Object.is(value, nextVal)) return;
|
|
19
|
+
value = nextVal;
|
|
20
|
+
notify(subs);
|
|
21
|
+
}
|
|
22
|
+
sig.set = (next) => {
|
|
23
|
+
const nextVal = typeof next === 'function' ? next(value) : next;
|
|
24
|
+
if (Object.is(value, nextVal)) return;
|
|
25
|
+
value = nextVal;
|
|
26
|
+
notify(subs);
|
|
27
|
+
};
|
|
28
|
+
sig.peek = () => value;
|
|
29
|
+
sig.subscribe = (fn) => {
|
|
30
|
+
return effect(() => fn(sig()));
|
|
31
|
+
};
|
|
32
|
+
sig._signal = true;
|
|
33
|
+
return sig;
|
|
34
|
+
}
|
|
48
35
|
export function computed(fn) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
read._signal = true;
|
|
75
|
-
read.peek = () => {
|
|
76
|
-
if (dirty) _runEffect(inner);
|
|
77
|
-
return value;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
return read;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// --- Effect ---
|
|
84
|
-
// Runs a function, auto-tracking signal reads. Re-runs when deps change.
|
|
85
|
-
// Returns a dispose function.
|
|
86
|
-
|
|
36
|
+
let value, dirty = true;
|
|
37
|
+
const subs = new Set();
|
|
38
|
+
const inner = _createEffect(() => {
|
|
39
|
+
value = fn();
|
|
40
|
+
dirty = false;
|
|
41
|
+
}, true);
|
|
42
|
+
function read() {
|
|
43
|
+
if (currentEffect) {
|
|
44
|
+
subs.add(currentEffect);
|
|
45
|
+
currentEffect.deps.push(subs);
|
|
46
|
+
}
|
|
47
|
+
if (dirty) _runEffect(inner);
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
inner._onNotify = () => {
|
|
51
|
+
dirty = true;
|
|
52
|
+
notify(subs);
|
|
53
|
+
};
|
|
54
|
+
read._signal = true;
|
|
55
|
+
read.peek = () => {
|
|
56
|
+
if (dirty) _runEffect(inner);
|
|
57
|
+
return value;
|
|
58
|
+
};
|
|
59
|
+
return read;
|
|
60
|
+
}
|
|
87
61
|
export function effect(fn, opts) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
return dispose;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// --- Batch ---
|
|
109
|
-
// Group multiple signal writes; effects run once at the end.
|
|
110
|
-
|
|
62
|
+
const e = _createEffect(fn);
|
|
63
|
+
const prev = currentEffect;
|
|
64
|
+
currentEffect = e;
|
|
65
|
+
try {
|
|
66
|
+
const result = e.fn();
|
|
67
|
+
if (typeof result === 'function') e._cleanup = result;
|
|
68
|
+
} finally {
|
|
69
|
+
currentEffect = prev;
|
|
70
|
+
}
|
|
71
|
+
if (opts?.stable) e._stable = true;
|
|
72
|
+
const dispose = () => _disposeEffect(e);
|
|
73
|
+
if (currentRoot) {
|
|
74
|
+
currentRoot.disposals.push(dispose);
|
|
75
|
+
}
|
|
76
|
+
return dispose;
|
|
77
|
+
}
|
|
111
78
|
export function batch(fn) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// --- Internals ---
|
|
122
|
-
|
|
79
|
+
batchDepth++;
|
|
80
|
+
try {
|
|
81
|
+
fn();
|
|
82
|
+
} finally {
|
|
83
|
+
batchDepth--;
|
|
84
|
+
if (batchDepth === 0) flush();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
123
87
|
function _createEffect(fn, lazy) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
88
|
+
return {
|
|
89
|
+
fn,
|
|
90
|
+
deps: [],
|
|
91
|
+
lazy: lazy || false,
|
|
92
|
+
_onNotify: null,
|
|
93
|
+
disposed: false,
|
|
94
|
+
_pending: false,
|
|
95
|
+
_stable: false,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
135
98
|
function _runEffect(e) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
e._cleanup = result;
|
|
173
|
-
}
|
|
174
|
-
} finally {
|
|
175
|
-
currentEffect = prev;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
99
|
+
if (e.disposed) return;
|
|
100
|
+
if (e._stable) {
|
|
101
|
+
if (e._cleanup) {
|
|
102
|
+
try { e._cleanup(); } catch (err) {
|
|
103
|
+
if (__DEV__) console.warn('[what] Error in effect cleanup:', err);
|
|
104
|
+
}
|
|
105
|
+
e._cleanup = null;
|
|
106
|
+
}
|
|
107
|
+
const prev = currentEffect;
|
|
108
|
+
currentEffect = null;
|
|
109
|
+
try {
|
|
110
|
+
const result = e.fn();
|
|
111
|
+
if (typeof result === 'function') e._cleanup = result;
|
|
112
|
+
} finally {
|
|
113
|
+
currentEffect = prev;
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
cleanup(e);
|
|
118
|
+
if (e._cleanup) {
|
|
119
|
+
try { e._cleanup(); } catch (err) {
|
|
120
|
+
if (__DEV__) console.warn('[what] Error in effect cleanup:', err);
|
|
121
|
+
}
|
|
122
|
+
e._cleanup = null;
|
|
123
|
+
}
|
|
124
|
+
const prev = currentEffect;
|
|
125
|
+
currentEffect = e;
|
|
126
|
+
try {
|
|
127
|
+
const result = e.fn();
|
|
128
|
+
if (typeof result === 'function') {
|
|
129
|
+
e._cleanup = result;
|
|
130
|
+
}
|
|
131
|
+
} finally {
|
|
132
|
+
currentEffect = prev;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
179
135
|
function _disposeEffect(e) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
136
|
+
e.disposed = true;
|
|
137
|
+
cleanup(e);
|
|
138
|
+
if (e._cleanup) {
|
|
139
|
+
try { e._cleanup(); } catch (err) {
|
|
140
|
+
if (__DEV__) console.warn('[what] Error in effect cleanup on dispose:', err);
|
|
141
|
+
}
|
|
142
|
+
e._cleanup = null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
191
145
|
function cleanup(e) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
146
|
+
const deps = e.deps;
|
|
147
|
+
for (let i = 0; i < deps.length; i++) deps[i].delete(e);
|
|
148
|
+
deps.length = 0;
|
|
195
149
|
}
|
|
196
|
-
|
|
197
150
|
function notify(subs) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (batchDepth === 0 && pendingEffects.length > 0) scheduleMicrotask();
|
|
224
|
-
}
|
|
225
|
-
|
|
151
|
+
for (const e of subs) {
|
|
152
|
+
if (e.disposed) continue;
|
|
153
|
+
if (e._onNotify) {
|
|
154
|
+
e._onNotify();
|
|
155
|
+
} else if (batchDepth === 0 && e._stable) {
|
|
156
|
+
const prev = currentEffect;
|
|
157
|
+
currentEffect = null;
|
|
158
|
+
try {
|
|
159
|
+
const result = e.fn();
|
|
160
|
+
if (typeof result === 'function') {
|
|
161
|
+
if (e._cleanup) try { e._cleanup(); } catch (err) {}
|
|
162
|
+
e._cleanup = result;
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (__DEV__) console.warn('[what] Error in stable effect:', err);
|
|
166
|
+
} finally {
|
|
167
|
+
currentEffect = prev;
|
|
168
|
+
}
|
|
169
|
+
} else if (!e._pending) {
|
|
170
|
+
e._pending = true;
|
|
171
|
+
pendingEffects.push(e);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (batchDepth === 0 && pendingEffects.length > 0) scheduleMicrotask();
|
|
175
|
+
}
|
|
226
176
|
let microtaskScheduled = false;
|
|
227
177
|
function scheduleMicrotask() {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
178
|
+
if (!microtaskScheduled) {
|
|
179
|
+
microtaskScheduled = true;
|
|
180
|
+
queueMicrotask(() => {
|
|
181
|
+
microtaskScheduled = false;
|
|
182
|
+
flush();
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
237
186
|
function flush() {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// --- Memo ---
|
|
268
|
-
// Eager computed that only propagates when the value actually changes.
|
|
269
|
-
// Reads deps eagerly (unlike lazy computed), but skips notifying subscribers
|
|
270
|
-
// when the recomputed value is the same. Critical for patterns like:
|
|
271
|
-
// memo(() => selected() === item().id) — 1000 memos, only 2 change
|
|
187
|
+
let iterations = 0;
|
|
188
|
+
while (pendingEffects.length > 0 && iterations < 100) {
|
|
189
|
+
const batch = pendingEffects;
|
|
190
|
+
pendingEffects = [];
|
|
191
|
+
for (let i = 0; i < batch.length; i++) {
|
|
192
|
+
const e = batch[i];
|
|
193
|
+
e._pending = false;
|
|
194
|
+
if (!e.disposed && !e._onNotify) _runEffect(e);
|
|
195
|
+
}
|
|
196
|
+
iterations++;
|
|
197
|
+
}
|
|
198
|
+
if (iterations >= 100) {
|
|
199
|
+
if (__DEV__) {
|
|
200
|
+
const remaining = pendingEffects.slice(0, 3);
|
|
201
|
+
const effectNames = remaining.map(e => e.fn?.name || e.fn?.toString().slice(0, 60) || '(anonymous)');
|
|
202
|
+
console.warn(
|
|
203
|
+
`[what] Possible infinite effect loop detected (100 iterations). ` +
|
|
204
|
+
`Likely cause: an effect writes to a signal it also reads, creating a cycle. ` +
|
|
205
|
+
`Use untrack() to read signals without subscribing. ` +
|
|
206
|
+
`Looping effects: ${effectNames.join(', ')}`
|
|
207
|
+
);
|
|
208
|
+
} else {
|
|
209
|
+
console.warn('[what] Possible infinite effect loop detected');
|
|
210
|
+
}
|
|
211
|
+
for (let i = 0; i < pendingEffects.length; i++) pendingEffects[i]._pending = false;
|
|
212
|
+
pendingEffects.length = 0;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
272
215
|
export function memo(fn) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
read._signal = true;
|
|
300
|
-
read.peek = () => value;
|
|
301
|
-
return read;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// --- flushSync ---
|
|
305
|
-
// Force all pending effects to run synchronously. Use sparingly.
|
|
216
|
+
let value;
|
|
217
|
+
const subs = new Set();
|
|
218
|
+
const e = _createEffect(() => {
|
|
219
|
+
const next = fn();
|
|
220
|
+
if (!Object.is(value, next)) {
|
|
221
|
+
value = next;
|
|
222
|
+
notify(subs);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
_runEffect(e);
|
|
226
|
+
if (currentRoot) {
|
|
227
|
+
currentRoot.disposals.push(() => _disposeEffect(e));
|
|
228
|
+
}
|
|
229
|
+
function read() {
|
|
230
|
+
if (currentEffect) {
|
|
231
|
+
subs.add(currentEffect);
|
|
232
|
+
currentEffect.deps.push(subs);
|
|
233
|
+
}
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
read._signal = true;
|
|
237
|
+
read.peek = () => value;
|
|
238
|
+
return read;
|
|
239
|
+
}
|
|
306
240
|
export function flushSync() {
|
|
307
|
-
|
|
308
|
-
|
|
241
|
+
microtaskScheduled = false;
|
|
242
|
+
flush();
|
|
309
243
|
}
|
|
310
|
-
|
|
311
|
-
// --- Untrack ---
|
|
312
|
-
// Read signals without subscribing
|
|
313
244
|
export function untrack(fn) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// --- createRoot ---
|
|
324
|
-
// Isolated reactive scope. All effects created inside are tracked and disposed together.
|
|
325
|
-
// Essential for per-item cleanup in reactive lists.
|
|
245
|
+
const prev = currentEffect;
|
|
246
|
+
currentEffect = null;
|
|
247
|
+
try {
|
|
248
|
+
return fn();
|
|
249
|
+
} finally {
|
|
250
|
+
currentEffect = prev;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
326
253
|
export function createRoot(fn) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
254
|
+
const prevRoot = currentRoot;
|
|
255
|
+
const root = { disposals: [], owner: currentRoot };
|
|
256
|
+
currentRoot = root;
|
|
257
|
+
try {
|
|
258
|
+
const dispose = () => {
|
|
259
|
+
for (let i = root.disposals.length - 1; i >= 0; i--) {
|
|
260
|
+
root.disposals[i]();
|
|
261
|
+
}
|
|
262
|
+
root.disposals.length = 0;
|
|
263
|
+
};
|
|
264
|
+
return fn(dispose);
|
|
265
|
+
} finally {
|
|
266
|
+
currentRoot = prevRoot;
|
|
341
267
|
}
|
|
268
|
+
}
|