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/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 = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
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();