zero-query 0.1.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/LICENSE +21 -0
- package/README.md +1271 -0
- package/index.js +173 -0
- package/package.json +48 -0
- package/src/component.js +800 -0
- package/src/core.js +508 -0
- package/src/http.js +177 -0
- package/src/reactive.js +125 -0
- package/src/router.js +334 -0
- package/src/store.js +169 -0
- package/src/utils.js +271 -0
package/src/utils.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zQuery Utils — Common utility functions
|
|
3
|
+
*
|
|
4
|
+
* Quality-of-life helpers that every frontend project needs.
|
|
5
|
+
* Attached to $ namespace for convenience.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Function utilities
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Debounce — delays execution until after `ms` of inactivity
|
|
14
|
+
*/
|
|
15
|
+
export function debounce(fn, ms = 250) {
|
|
16
|
+
let timer;
|
|
17
|
+
const debounced = (...args) => {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
timer = setTimeout(() => fn(...args), ms);
|
|
20
|
+
};
|
|
21
|
+
debounced.cancel = () => clearTimeout(timer);
|
|
22
|
+
return debounced;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Throttle — limits execution to once per `ms`
|
|
27
|
+
*/
|
|
28
|
+
export function throttle(fn, ms = 250) {
|
|
29
|
+
let last = 0;
|
|
30
|
+
let timer;
|
|
31
|
+
return (...args) => {
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
const remaining = ms - (now - last);
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
if (remaining <= 0) {
|
|
36
|
+
last = now;
|
|
37
|
+
fn(...args);
|
|
38
|
+
} else {
|
|
39
|
+
timer = setTimeout(() => { last = Date.now(); fn(...args); }, remaining);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Pipe — compose functions left-to-right
|
|
46
|
+
*/
|
|
47
|
+
export function pipe(...fns) {
|
|
48
|
+
return (input) => fns.reduce((val, fn) => fn(val), input);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Once — function that only runs once
|
|
53
|
+
*/
|
|
54
|
+
export function once(fn) {
|
|
55
|
+
let called = false, result;
|
|
56
|
+
return (...args) => {
|
|
57
|
+
if (!called) { called = true; result = fn(...args); }
|
|
58
|
+
return result;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Sleep — promise-based delay
|
|
64
|
+
*/
|
|
65
|
+
export function sleep(ms) {
|
|
66
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// String utilities
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Escape HTML entities
|
|
76
|
+
*/
|
|
77
|
+
export function escapeHtml(str) {
|
|
78
|
+
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
79
|
+
return String(str).replace(/[&<>"']/g, c => map[c]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Template tag for auto-escaping interpolated values
|
|
84
|
+
* Usage: $.html`<div>${userInput}</div>`
|
|
85
|
+
*/
|
|
86
|
+
export function html(strings, ...values) {
|
|
87
|
+
return strings.reduce((result, str, i) => {
|
|
88
|
+
const val = values[i - 1];
|
|
89
|
+
const escaped = (val instanceof TrustedHTML) ? val.toString() : escapeHtml(val ?? '');
|
|
90
|
+
return result + escaped + str;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Mark HTML as trusted (skip escaping in $.html template)
|
|
96
|
+
*/
|
|
97
|
+
class TrustedHTML {
|
|
98
|
+
constructor(html) { this._html = html; }
|
|
99
|
+
toString() { return this._html; }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function trust(htmlStr) {
|
|
103
|
+
return new TrustedHTML(htmlStr);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Generate UUID v4
|
|
108
|
+
*/
|
|
109
|
+
export function uuid() {
|
|
110
|
+
return crypto?.randomUUID?.() || 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
111
|
+
const r = Math.random() * 16 | 0;
|
|
112
|
+
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Kebab-case to camelCase
|
|
118
|
+
*/
|
|
119
|
+
export function camelCase(str) {
|
|
120
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* CamelCase to kebab-case
|
|
125
|
+
*/
|
|
126
|
+
export function kebabCase(str) {
|
|
127
|
+
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Object utilities
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Deep clone
|
|
137
|
+
*/
|
|
138
|
+
export function deepClone(obj) {
|
|
139
|
+
if (typeof structuredClone === 'function') return structuredClone(obj);
|
|
140
|
+
return JSON.parse(JSON.stringify(obj));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Deep merge objects
|
|
145
|
+
*/
|
|
146
|
+
export function deepMerge(target, ...sources) {
|
|
147
|
+
for (const source of sources) {
|
|
148
|
+
for (const key of Object.keys(source)) {
|
|
149
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
150
|
+
if (!target[key] || typeof target[key] !== 'object') target[key] = {};
|
|
151
|
+
deepMerge(target[key], source[key]);
|
|
152
|
+
} else {
|
|
153
|
+
target[key] = source[key];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return target;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Simple object equality check
|
|
162
|
+
*/
|
|
163
|
+
export function isEqual(a, b) {
|
|
164
|
+
if (a === b) return true;
|
|
165
|
+
if (typeof a !== typeof b) return false;
|
|
166
|
+
if (typeof a !== 'object' || a === null || b === null) return false;
|
|
167
|
+
const keysA = Object.keys(a);
|
|
168
|
+
const keysB = Object.keys(b);
|
|
169
|
+
if (keysA.length !== keysB.length) return false;
|
|
170
|
+
return keysA.every(k => isEqual(a[k], b[k]));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// URL utilities
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Serialize object to URL query string
|
|
180
|
+
*/
|
|
181
|
+
export function param(obj) {
|
|
182
|
+
return new URLSearchParams(obj).toString();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Parse URL query string to object
|
|
187
|
+
*/
|
|
188
|
+
export function parseQuery(str) {
|
|
189
|
+
return Object.fromEntries(new URLSearchParams(str));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Storage helpers (localStorage wrapper with JSON support)
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
export const storage = {
|
|
197
|
+
get(key, fallback = null) {
|
|
198
|
+
try {
|
|
199
|
+
const raw = localStorage.getItem(key);
|
|
200
|
+
return raw !== null ? JSON.parse(raw) : fallback;
|
|
201
|
+
} catch {
|
|
202
|
+
return fallback;
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
set(key, value) {
|
|
207
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
remove(key) {
|
|
211
|
+
localStorage.removeItem(key);
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
clear() {
|
|
215
|
+
localStorage.clear();
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const session = {
|
|
220
|
+
get(key, fallback = null) {
|
|
221
|
+
try {
|
|
222
|
+
const raw = sessionStorage.getItem(key);
|
|
223
|
+
return raw !== null ? JSON.parse(raw) : fallback;
|
|
224
|
+
} catch {
|
|
225
|
+
return fallback;
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
set(key, value) {
|
|
230
|
+
sessionStorage.setItem(key, JSON.stringify(value));
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
remove(key) {
|
|
234
|
+
sessionStorage.removeItem(key);
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
clear() {
|
|
238
|
+
sessionStorage.clear();
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// Event bus (pub/sub)
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
class EventBus {
|
|
247
|
+
constructor() { this._handlers = new Map(); }
|
|
248
|
+
|
|
249
|
+
on(event, fn) {
|
|
250
|
+
if (!this._handlers.has(event)) this._handlers.set(event, new Set());
|
|
251
|
+
this._handlers.get(event).add(fn);
|
|
252
|
+
return () => this.off(event, fn);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
off(event, fn) {
|
|
256
|
+
this._handlers.get(event)?.delete(fn);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
emit(event, ...args) {
|
|
260
|
+
this._handlers.get(event)?.forEach(fn => fn(...args));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
once(event, fn) {
|
|
264
|
+
const wrapper = (...args) => { fn(...args); this.off(event, wrapper); };
|
|
265
|
+
return this.on(event, wrapper);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
clear() { this._handlers.clear(); }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export const bus = new EventBus();
|