what-core 0.4.2 → 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 -915
- 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 +91 -8
- 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/helpers.js
CHANGED
|
@@ -1,203 +1,140 @@
|
|
|
1
|
-
|
|
2
|
-
// Commonly needed patterns, zero overhead.
|
|
3
|
-
|
|
4
|
-
import { signal, effect, computed, batch, __DEV__ } from './reactive.js';
|
|
5
|
-
|
|
6
|
-
// --- show(condition, vnode) --- [DEPRECATED: use <Show> component instead]
|
|
7
|
-
// Conditional rendering. More readable than ternary.
|
|
8
|
-
let _showWarned = false;
|
|
9
|
-
export function show(condition, vnode, fallback = null) {
|
|
10
|
-
if (!_showWarned) {
|
|
11
|
-
_showWarned = true;
|
|
12
|
-
console.warn('[what] show() is deprecated. Use the <Show> component or ternary expressions instead.');
|
|
13
|
-
}
|
|
14
|
-
return condition ? vnode : fallback;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// --- each(list, fn) --- [DEPRECATED: use <For> component or .map() instead]
|
|
18
|
-
// Keyed list rendering. Optimized for reconciliation.
|
|
1
|
+
import { signal, effect, __DEV__ } from './reactive.js';
|
|
19
2
|
let _eachWarned = false;
|
|
20
3
|
export function each(list, fn, keyFn) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// --- cls(...args) ---
|
|
36
|
-
// Conditional class names. Like clsx but tiny.
|
|
37
|
-
// cls('base', condition && 'active', { hidden: isHidden, bold: isBold })
|
|
4
|
+
if (!_eachWarned) {
|
|
5
|
+
_eachWarned = true;
|
|
6
|
+
console.warn('[what] each() is deprecated. Use the <For> component or Array.map() instead.');
|
|
7
|
+
}
|
|
8
|
+
if (!list || list.length === 0) return [];
|
|
9
|
+
return list.map((item, index) => {
|
|
10
|
+
const vnode = fn(item, index);
|
|
11
|
+
if (keyFn && vnode && typeof vnode === 'object') {
|
|
12
|
+
vnode.key = keyFn(item, index);
|
|
13
|
+
}
|
|
14
|
+
return vnode;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
38
17
|
export function cls(...args) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// --- style(obj) ---
|
|
54
|
-
// Convert a style object to a CSS string for SSR.
|
|
18
|
+
const classes = [];
|
|
19
|
+
for (const arg of args) {
|
|
20
|
+
if (!arg) continue;
|
|
21
|
+
if (typeof arg === 'string') {
|
|
22
|
+
classes.push(arg);
|
|
23
|
+
} else if (typeof arg === 'object') {
|
|
24
|
+
for (const [key, val] of Object.entries(arg)) {
|
|
25
|
+
if (val) classes.push(key);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return classes.join(' ');
|
|
30
|
+
}
|
|
55
31
|
export function style(obj) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
32
|
+
if (typeof obj === 'string') return obj;
|
|
33
|
+
return Object.entries(obj)
|
|
34
|
+
.filter(([, v]) => v != null && v !== '')
|
|
35
|
+
.map(([k, v]) => `${camelToKebab(k)}:${v}`)
|
|
36
|
+
.join(';');
|
|
61
37
|
}
|
|
62
|
-
|
|
63
38
|
function camelToKebab(str) {
|
|
64
|
-
|
|
39
|
+
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
65
40
|
}
|
|
66
|
-
|
|
67
|
-
// --- debounce ---
|
|
68
|
-
// Debounced signal updates.
|
|
69
41
|
export function debounce(fn, ms) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// --- throttle ---
|
|
42
|
+
let timer;
|
|
43
|
+
return (...args) => {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
timer = setTimeout(() => fn(...args), ms);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
78
48
|
export function throttle(fn, ms) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Component context ref — injected by dom.js to avoid circular imports
|
|
49
|
+
let last = 0;
|
|
50
|
+
return (...args) => {
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
if (now - last >= ms) {
|
|
53
|
+
last = now;
|
|
54
|
+
fn(...args);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
90
58
|
let _getCurrentComponentRef = null;
|
|
91
59
|
export function _setComponentRef(fn) { _getCurrentComponentRef = fn; }
|
|
92
|
-
|
|
93
|
-
// --- useMediaQuery ---
|
|
94
|
-
// Reactive media query. Returns a signal. Cleans up listener on component unmount.
|
|
95
60
|
export function useMediaQuery(query) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return s;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// --- useLocalStorage ---
|
|
113
|
-
// Signal synced with localStorage. Cleans up listeners on component unmount.
|
|
61
|
+
if (typeof window === 'undefined') return signal(false);
|
|
62
|
+
const mq = window.matchMedia(query);
|
|
63
|
+
const s = signal(mq.matches);
|
|
64
|
+
const handler = (e) => s.set(e.matches);
|
|
65
|
+
mq.addEventListener('change', handler);
|
|
66
|
+
const ctx = _getCurrentComponentRef?.();
|
|
67
|
+
if (ctx) {
|
|
68
|
+
ctx._cleanupCallbacks = ctx._cleanupCallbacks || [];
|
|
69
|
+
ctx._cleanupCallbacks.push(() => mq.removeEventListener('change', handler));
|
|
70
|
+
}
|
|
71
|
+
return s;
|
|
72
|
+
}
|
|
114
73
|
export function useLocalStorage(key, initial) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
ctx._cleanupCallbacks.push(() => {
|
|
152
|
-
dispose();
|
|
153
|
-
if (storageHandler) window.removeEventListener('storage', storageHandler);
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return s;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// --- portal ---
|
|
161
|
-
// Render children into a different DOM container.
|
|
74
|
+
let stored;
|
|
75
|
+
try {
|
|
76
|
+
const raw = localStorage.getItem(key);
|
|
77
|
+
stored = raw !== null ? JSON.parse(raw) : initial;
|
|
78
|
+
} catch {
|
|
79
|
+
stored = initial;
|
|
80
|
+
}
|
|
81
|
+
const s = signal(stored);
|
|
82
|
+
const dispose = effect(() => {
|
|
83
|
+
try {
|
|
84
|
+
localStorage.setItem(key, JSON.stringify(s()));
|
|
85
|
+
} catch (e) {
|
|
86
|
+
if (__DEV__) console.warn('[what] localStorage write failed (quota exceeded?):', e);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
let storageHandler = null;
|
|
90
|
+
if (typeof window !== 'undefined') {
|
|
91
|
+
storageHandler = (e) => {
|
|
92
|
+
if (e.key === key && e.newValue !== null) {
|
|
93
|
+
try { s.set(JSON.parse(e.newValue)); } catch (err) {
|
|
94
|
+
if (__DEV__) console.warn('[what] localStorage parse failed:', err);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
window.addEventListener('storage', storageHandler);
|
|
99
|
+
}
|
|
100
|
+
const ctx = _getCurrentComponentRef?.();
|
|
101
|
+
if (ctx) {
|
|
102
|
+
ctx._cleanupCallbacks = ctx._cleanupCallbacks || [];
|
|
103
|
+
ctx._cleanupCallbacks.push(() => {
|
|
104
|
+
dispose();
|
|
105
|
+
if (storageHandler) window.removeEventListener('storage', storageHandler);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return s;
|
|
109
|
+
}
|
|
162
110
|
export function Portal({ target, children }) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return { tag: '__portal', props: { container }, children: Array.isArray(children) ? children : [children], _vnode: true };
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// --- useClickOutside ---
|
|
174
|
-
// Detect clicks outside a ref'd element. Essential for dropdowns, modals, popovers.
|
|
111
|
+
if (typeof document === 'undefined') return null;
|
|
112
|
+
const container = typeof target === 'string'
|
|
113
|
+
? document.querySelector(target)
|
|
114
|
+
: target;
|
|
115
|
+
if (!container) return null;
|
|
116
|
+
return { tag: '__portal', props: { container }, children: Array.isArray(children) ? children : [children], _vnode: true };
|
|
117
|
+
}
|
|
175
118
|
export function useClickOutside(ref, handler) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
document.removeEventListener('mousedown', listener);
|
|
192
|
-
document.removeEventListener('touchstart', listener);
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// --- Transition helper ---
|
|
198
|
-
// Animate elements in/out. Returns props to spread on the element.
|
|
199
|
-
export function transition(name, active) {
|
|
200
|
-
return {
|
|
201
|
-
class: active ? `${name}-enter ${name}-enter-active` : `${name}-leave ${name}-leave-active`,
|
|
202
|
-
};
|
|
119
|
+
if (typeof document === 'undefined') return;
|
|
120
|
+
const listener = (e) => {
|
|
121
|
+
const el = ref.current || ref;
|
|
122
|
+
if (!el || el.contains(e.target)) return;
|
|
123
|
+
handler(e);
|
|
124
|
+
};
|
|
125
|
+
document.addEventListener('mousedown', listener);
|
|
126
|
+
document.addEventListener('touchstart', listener);
|
|
127
|
+
const ctx = _getCurrentComponentRef?.();
|
|
128
|
+
if (ctx) {
|
|
129
|
+
ctx._cleanupCallbacks = ctx._cleanupCallbacks || [];
|
|
130
|
+
ctx._cleanupCallbacks.push(() => {
|
|
131
|
+
document.removeEventListener('mousedown', listener);
|
|
132
|
+
document.removeEventListener('touchstart', listener);
|
|
133
|
+
});
|
|
203
134
|
}
|
|
135
|
+
}
|
|
136
|
+
export function transition(name, active) {
|
|
137
|
+
return {
|
|
138
|
+
class: active ? `${name}-enter ${name}-enter-active` : `${name}-leave ${name}-leave-active`,
|
|
139
|
+
};
|
|
140
|
+
}
|