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/helpers.js CHANGED
@@ -1,203 +1,140 @@
1
- // What Framework - Helpers & Utilities
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
- if (!_eachWarned) {
22
- _eachWarned = true;
23
- console.warn('[what] each() is deprecated. Use the <For> component or Array.map() instead.');
24
- }
25
- if (!list || list.length === 0) return [];
26
- return list.map((item, index) => {
27
- const vnode = fn(item, index);
28
- if (keyFn && vnode && typeof vnode === 'object') {
29
- vnode.key = keyFn(item, index);
30
- }
31
- return vnode;
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
- const classes = [];
40
- for (const arg of args) {
41
- if (!arg) continue;
42
- if (typeof arg === 'string') {
43
- classes.push(arg);
44
- } else if (typeof arg === 'object') {
45
- for (const [key, val] of Object.entries(arg)) {
46
- if (val) classes.push(key);
47
- }
48
- }
49
- }
50
- return classes.join(' ');
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
- if (typeof obj === 'string') return obj;
57
- return Object.entries(obj)
58
- .filter(([, v]) => v != null && v !== '')
59
- .map(([k, v]) => `${camelToKebab(k)}:${v}`)
60
- .join(';');
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
- return str.replace(/([A-Z])/g, '-$1').toLowerCase();
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
- let timer;
71
- return (...args) => {
72
- clearTimeout(timer);
73
- timer = setTimeout(() => fn(...args), ms);
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
- let last = 0;
80
- return (...args) => {
81
- const now = Date.now();
82
- if (now - last >= ms) {
83
- last = now;
84
- fn(...args);
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
- if (typeof window === 'undefined') return signal(false);
97
- const mq = window.matchMedia(query);
98
- const s = signal(mq.matches);
99
- const handler = (e) => s.set(e.matches);
100
- mq.addEventListener('change', handler);
101
-
102
- // Register cleanup if inside a component context
103
- const ctx = _getCurrentComponentRef?.();
104
- if (ctx) {
105
- ctx._cleanupCallbacks = ctx._cleanupCallbacks || [];
106
- ctx._cleanupCallbacks.push(() => mq.removeEventListener('change', handler));
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
- let stored;
116
- try {
117
- const raw = localStorage.getItem(key);
118
- stored = raw !== null ? JSON.parse(raw) : initial;
119
- } catch {
120
- stored = initial;
121
- }
122
-
123
- const s = signal(stored);
124
-
125
- // Sync to localStorage on changes
126
- const dispose = effect(() => {
127
- try {
128
- localStorage.setItem(key, JSON.stringify(s()));
129
- } catch (e) {
130
- if (__DEV__) console.warn('[what] localStorage write failed (quota exceeded?):', e);
131
- }
132
- });
133
-
134
- // Listen for changes from other tabs
135
- let storageHandler = null;
136
- if (typeof window !== 'undefined') {
137
- storageHandler = (e) => {
138
- if (e.key === key && e.newValue !== null) {
139
- try { s.set(JSON.parse(e.newValue)); } catch (err) {
140
- if (__DEV__) console.warn('[what] localStorage parse failed:', err);
141
- }
142
- }
143
- };
144
- window.addEventListener('storage', storageHandler);
145
- }
146
-
147
- // Register cleanup if inside a component context
148
- const ctx = _getCurrentComponentRef?.();
149
- if (ctx) {
150
- ctx._cleanupCallbacks = ctx._cleanupCallbacks || [];
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
- // In SSR, just return null (portals are client-only)
164
- if (typeof document === 'undefined') return null;
165
- const container = typeof target === 'string'
166
- ? document.querySelector(target)
167
- : target;
168
- if (!container) return null;
169
- // The DOM reconciler will mount children here
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
- if (typeof document === 'undefined') return;
177
-
178
- const listener = (e) => {
179
- const el = ref.current || ref;
180
- if (!el || el.contains(e.target)) return;
181
- handler(e);
182
- };
183
-
184
- document.addEventListener('mousedown', listener);
185
- document.addEventListener('touchstart', listener);
186
-
187
- const ctx = _getCurrentComponentRef?.();
188
- if (ctx) {
189
- ctx._cleanupCallbacks = ctx._cleanupCallbacks || [];
190
- ctx._cleanupCallbacks.push(() => {
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
+ }