@bquery/bquery 1.0.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.
Files changed (80) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +266 -0
  3. package/dist/component/index.d.ts +155 -0
  4. package/dist/component/index.d.ts.map +1 -0
  5. package/dist/component.es.mjs +128 -0
  6. package/dist/component.es.mjs.map +1 -0
  7. package/dist/core/collection.d.ts +198 -0
  8. package/dist/core/collection.d.ts.map +1 -0
  9. package/dist/core/element.d.ts +301 -0
  10. package/dist/core/element.d.ts.map +1 -0
  11. package/dist/core/index.d.ts +5 -0
  12. package/dist/core/index.d.ts.map +1 -0
  13. package/dist/core/selector.d.ts +11 -0
  14. package/dist/core/selector.d.ts.map +1 -0
  15. package/dist/core/shared.d.ts +7 -0
  16. package/dist/core/shared.d.ts.map +1 -0
  17. package/dist/core/utils.d.ts +300 -0
  18. package/dist/core/utils.d.ts.map +1 -0
  19. package/dist/core.es.mjs +1015 -0
  20. package/dist/core.es.mjs.map +1 -0
  21. package/dist/full.d.ts +48 -0
  22. package/dist/full.d.ts.map +1 -0
  23. package/dist/full.es.mjs +43 -0
  24. package/dist/full.es.mjs.map +1 -0
  25. package/dist/full.iife.js +2 -0
  26. package/dist/full.iife.js.map +1 -0
  27. package/dist/full.umd.js +2 -0
  28. package/dist/full.umd.js.map +1 -0
  29. package/dist/index.d.ts +16 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.es.mjs +43 -0
  32. package/dist/index.es.mjs.map +1 -0
  33. package/dist/motion/index.d.ts +145 -0
  34. package/dist/motion/index.d.ts.map +1 -0
  35. package/dist/motion.es.mjs +104 -0
  36. package/dist/motion.es.mjs.map +1 -0
  37. package/dist/platform/buckets.d.ts +44 -0
  38. package/dist/platform/buckets.d.ts.map +1 -0
  39. package/dist/platform/cache.d.ts +71 -0
  40. package/dist/platform/cache.d.ts.map +1 -0
  41. package/dist/platform/index.d.ts +15 -0
  42. package/dist/platform/index.d.ts.map +1 -0
  43. package/dist/platform/notifications.d.ts +52 -0
  44. package/dist/platform/notifications.d.ts.map +1 -0
  45. package/dist/platform/storage.d.ts +69 -0
  46. package/dist/platform/storage.d.ts.map +1 -0
  47. package/dist/platform.es.mjs +245 -0
  48. package/dist/platform.es.mjs.map +1 -0
  49. package/dist/reactive/index.d.ts +8 -0
  50. package/dist/reactive/index.d.ts.map +1 -0
  51. package/dist/reactive/signal.d.ts +204 -0
  52. package/dist/reactive/signal.d.ts.map +1 -0
  53. package/dist/reactive.es.mjs +123 -0
  54. package/dist/reactive.es.mjs.map +1 -0
  55. package/dist/security/index.d.ts +8 -0
  56. package/dist/security/index.d.ts.map +1 -0
  57. package/dist/security/sanitize.d.ts +99 -0
  58. package/dist/security/sanitize.d.ts.map +1 -0
  59. package/dist/security.es.mjs +194 -0
  60. package/dist/security.es.mjs.map +1 -0
  61. package/package.json +120 -0
  62. package/src/component/index.ts +360 -0
  63. package/src/core/collection.ts +339 -0
  64. package/src/core/element.ts +493 -0
  65. package/src/core/index.ts +4 -0
  66. package/src/core/selector.ts +29 -0
  67. package/src/core/shared.ts +13 -0
  68. package/src/core/utils.ts +425 -0
  69. package/src/full.ts +101 -0
  70. package/src/index.ts +27 -0
  71. package/src/motion/index.ts +365 -0
  72. package/src/platform/buckets.ts +115 -0
  73. package/src/platform/cache.ts +130 -0
  74. package/src/platform/index.ts +18 -0
  75. package/src/platform/notifications.ts +87 -0
  76. package/src/platform/storage.ts +208 -0
  77. package/src/reactive/index.ts +9 -0
  78. package/src/reactive/signal.ts +347 -0
  79. package/src/security/index.ts +18 -0
  80. package/src/security/sanitize.ts +446 -0
@@ -0,0 +1,365 @@
1
+ /**
2
+ * Motion module providing view transitions, FLIP animations, and spring physics.
3
+ * Designed to work with modern browser APIs while providing smooth fallbacks.
4
+ *
5
+ * @module bquery/motion
6
+ */
7
+
8
+ // ============================================================================
9
+ // Types
10
+ // ============================================================================
11
+
12
+ /**
13
+ * Options for view transitions.
14
+ */
15
+ export interface TransitionOptions {
16
+ /** The DOM update function to execute during transition */
17
+ update: () => void;
18
+ }
19
+
20
+ /**
21
+ * Captured element bounds for FLIP animations.
22
+ */
23
+ export interface ElementBounds {
24
+ top: number;
25
+ left: number;
26
+ width: number;
27
+ height: number;
28
+ }
29
+
30
+ /**
31
+ * FLIP animation configuration options.
32
+ */
33
+ export interface FlipOptions {
34
+ /** Animation duration in milliseconds */
35
+ duration?: number;
36
+ /** CSS easing function */
37
+ easing?: string;
38
+ /** Callback when animation completes */
39
+ onComplete?: () => void;
40
+ }
41
+
42
+ /**
43
+ * Spring physics configuration.
44
+ */
45
+ export interface SpringConfig {
46
+ /** Spring stiffness (default: 100) */
47
+ stiffness?: number;
48
+ /** Damping coefficient (default: 10) */
49
+ damping?: number;
50
+ /** Mass of the object (default: 1) */
51
+ mass?: number;
52
+ /** Velocity threshold for completion (default: 0.01) */
53
+ precision?: number;
54
+ }
55
+
56
+ /**
57
+ * Spring instance for animating values.
58
+ */
59
+ export interface Spring {
60
+ /** Start animating to target value */
61
+ to(target: number): Promise<void>;
62
+ /** Get current animated value */
63
+ current(): number;
64
+ /** Stop the animation */
65
+ stop(): void;
66
+ /** Subscribe to value changes */
67
+ onChange(callback: (value: number) => void): () => void;
68
+ }
69
+
70
+ // ============================================================================
71
+ // View Transitions
72
+ // ============================================================================
73
+
74
+ /** Extended document type with View Transitions API */
75
+ type DocumentWithTransition = Document & {
76
+ startViewTransition?: (callback: () => void) => {
77
+ finished: Promise<void>;
78
+ ready: Promise<void>;
79
+ updateCallbackDone: Promise<void>;
80
+ };
81
+ };
82
+
83
+ /**
84
+ * Execute a DOM update with view transition animation.
85
+ * Falls back to immediate update when View Transitions API is unavailable.
86
+ *
87
+ * @param updateOrOptions - Update function or options object
88
+ * @returns Promise that resolves when transition completes
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * await transition(() => {
93
+ * $('#content').text('Updated');
94
+ * });
95
+ * ```
96
+ */
97
+ export const transition = async (
98
+ updateOrOptions: (() => void) | TransitionOptions
99
+ ): Promise<void> => {
100
+ const update = typeof updateOrOptions === 'function' ? updateOrOptions : updateOrOptions.update;
101
+
102
+ const doc = document as DocumentWithTransition;
103
+
104
+ if (doc.startViewTransition) {
105
+ await doc.startViewTransition(() => update()).finished;
106
+ return;
107
+ }
108
+
109
+ update();
110
+ };
111
+
112
+ // ============================================================================
113
+ // FLIP Animations
114
+ // ============================================================================
115
+
116
+ /**
117
+ * Capture the current bounds of an element for FLIP animation.
118
+ *
119
+ * @param element - The DOM element to measure
120
+ * @returns The element's current position and size
121
+ */
122
+ export const capturePosition = (element: Element): ElementBounds => {
123
+ const rect = element.getBoundingClientRect();
124
+ return {
125
+ top: rect.top,
126
+ left: rect.left,
127
+ width: rect.width,
128
+ height: rect.height,
129
+ };
130
+ };
131
+
132
+ /**
133
+ * Perform a FLIP (First, Last, Invert, Play) animation.
134
+ * Animates an element from its captured position to its current position.
135
+ *
136
+ * @param element - The element to animate
137
+ * @param firstBounds - The previously captured bounds
138
+ * @param options - Animation configuration
139
+ * @returns Promise that resolves when animation completes
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * const first = capturePosition(element);
144
+ * // ... DOM changes that move the element ...
145
+ * await flip(element, first, { duration: 300 });
146
+ * ```
147
+ */
148
+ export const flip = (
149
+ element: Element,
150
+ firstBounds: ElementBounds,
151
+ options: FlipOptions = {}
152
+ ): Promise<void> => {
153
+ const { duration = 300, easing = 'ease-out', onComplete } = options;
154
+
155
+ // Last: Get current position
156
+ const lastBounds = capturePosition(element);
157
+
158
+ // Skip animation if element has zero dimensions (avoid division by zero)
159
+ if (lastBounds.width === 0 || lastBounds.height === 0) {
160
+ return Promise.resolve();
161
+ }
162
+
163
+ // Invert: Calculate the delta
164
+ const deltaX = firstBounds.left - lastBounds.left;
165
+ const deltaY = firstBounds.top - lastBounds.top;
166
+ const deltaW = firstBounds.width / lastBounds.width;
167
+ const deltaH = firstBounds.height / lastBounds.height;
168
+
169
+ // Skip animation if no change
170
+ if (deltaX === 0 && deltaY === 0 && deltaW === 1 && deltaH === 1) {
171
+ return Promise.resolve();
172
+ }
173
+
174
+ const htmlElement = element as HTMLElement;
175
+
176
+ // Apply inverted transform
177
+ htmlElement.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH})`;
178
+ htmlElement.style.transformOrigin = 'top left';
179
+
180
+ // Force reflow
181
+ void htmlElement.offsetHeight;
182
+
183
+ // Play: Animate back to current position
184
+ return new Promise((resolve) => {
185
+ const animation = htmlElement.animate(
186
+ [
187
+ {
188
+ transform: `translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH})`,
189
+ },
190
+ { transform: 'translate(0, 0) scale(1, 1)' },
191
+ ],
192
+ { duration, easing, fill: 'forwards' }
193
+ );
194
+
195
+ animation.onfinish = () => {
196
+ htmlElement.style.transform = '';
197
+ htmlElement.style.transformOrigin = '';
198
+ onComplete?.();
199
+ resolve();
200
+ };
201
+ });
202
+ };
203
+
204
+ /**
205
+ * FLIP helper for animating a list of elements.
206
+ * Useful for reordering lists with smooth animations.
207
+ *
208
+ * @param elements - Array of elements to animate
209
+ * @param performUpdate - Function that performs the DOM update
210
+ * @param options - Animation configuration
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * await flipList(listItems, () => {
215
+ * container.appendChild(container.firstChild); // Move first to last
216
+ * });
217
+ * ```
218
+ */
219
+ export const flipList = async (
220
+ elements: Element[],
221
+ performUpdate: () => void,
222
+ options: FlipOptions = {}
223
+ ): Promise<void> => {
224
+ // First: Capture all positions
225
+ const positions = new Map<Element, ElementBounds>();
226
+ for (const el of elements) {
227
+ positions.set(el, capturePosition(el));
228
+ }
229
+
230
+ // Perform DOM update
231
+ performUpdate();
232
+
233
+ // Animate each element
234
+ const animations = elements.map((el) => {
235
+ const first = positions.get(el);
236
+ if (!first) return Promise.resolve();
237
+ return flip(el, first, options);
238
+ });
239
+
240
+ await Promise.all(animations);
241
+ };
242
+
243
+ // ============================================================================
244
+ // Spring Physics
245
+ // ============================================================================
246
+
247
+ /**
248
+ * Default spring configuration values.
249
+ */
250
+ const DEFAULT_SPRING_CONFIG: Required<SpringConfig> = {
251
+ stiffness: 100,
252
+ damping: 10,
253
+ mass: 1,
254
+ precision: 0.01,
255
+ };
256
+
257
+ /**
258
+ * Create a spring-based animation for smooth, physics-based motion.
259
+ *
260
+ * @param initialValue - Starting value for the spring
261
+ * @param config - Spring physics configuration
262
+ * @returns Spring instance for controlling the animation
263
+ *
264
+ * @example
265
+ * ```ts
266
+ * const x = spring(0, { stiffness: 120, damping: 14 });
267
+ * x.onChange((value) => {
268
+ * element.style.transform = `translateX(${value}px)`;
269
+ * });
270
+ * await x.to(100);
271
+ * ```
272
+ */
273
+ export const spring = (initialValue: number, config: SpringConfig = {}): Spring => {
274
+ const { stiffness, damping, mass, precision } = {
275
+ ...DEFAULT_SPRING_CONFIG,
276
+ ...config,
277
+ };
278
+
279
+ let current = initialValue;
280
+ let velocity = 0;
281
+ let target = initialValue;
282
+ let animationFrame: number | null = null;
283
+ let resolvePromise: (() => void) | null = null;
284
+ const listeners = new Set<(value: number) => void>();
285
+
286
+ const notifyListeners = () => {
287
+ for (const listener of listeners) {
288
+ listener(current);
289
+ }
290
+ };
291
+
292
+ const step = () => {
293
+ // Spring physics calculation
294
+ const displacement = current - target;
295
+ const springForce = -stiffness * displacement;
296
+ const dampingForce = -damping * velocity;
297
+ const acceleration = (springForce + dampingForce) / mass;
298
+
299
+ velocity += acceleration * (1 / 60); // Assuming 60fps
300
+ current += velocity * (1 / 60);
301
+
302
+ notifyListeners();
303
+
304
+ // Check if spring has settled
305
+ if (Math.abs(velocity) < precision && Math.abs(displacement) < precision) {
306
+ current = target;
307
+ velocity = 0;
308
+ animationFrame = null;
309
+ notifyListeners();
310
+ resolvePromise?.();
311
+ resolvePromise = null;
312
+ return;
313
+ }
314
+
315
+ animationFrame = requestAnimationFrame(step);
316
+ };
317
+
318
+ return {
319
+ to(newTarget: number): Promise<void> {
320
+ target = newTarget;
321
+
322
+ if (animationFrame !== null) {
323
+ cancelAnimationFrame(animationFrame);
324
+ }
325
+
326
+ return new Promise((resolve) => {
327
+ resolvePromise = resolve;
328
+ animationFrame = requestAnimationFrame(step);
329
+ });
330
+ },
331
+
332
+ current(): number {
333
+ return current;
334
+ },
335
+
336
+ stop(): void {
337
+ if (animationFrame !== null) {
338
+ cancelAnimationFrame(animationFrame);
339
+ animationFrame = null;
340
+ }
341
+ velocity = 0;
342
+ resolvePromise?.();
343
+ resolvePromise = null;
344
+ },
345
+
346
+ onChange(callback: (value: number) => void): () => void {
347
+ listeners.add(callback);
348
+ return () => listeners.delete(callback);
349
+ },
350
+ };
351
+ };
352
+
353
+ /**
354
+ * Preset spring configurations for common use cases.
355
+ */
356
+ export const springPresets = {
357
+ /** Gentle, slow-settling spring */
358
+ gentle: { stiffness: 80, damping: 15 } as SpringConfig,
359
+ /** Responsive, snappy spring */
360
+ snappy: { stiffness: 200, damping: 20 } as SpringConfig,
361
+ /** Bouncy, playful spring */
362
+ bouncy: { stiffness: 300, damping: 8 } as SpringConfig,
363
+ /** Stiff, quick spring with minimal overshoot */
364
+ stiff: { stiffness: 400, damping: 30 } as SpringConfig,
365
+ };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Storage Buckets API wrapper.
3
+ * Provides a simplified interface for storing blobs and binary data.
4
+ * Falls back to IndexedDB when Storage Buckets API is not available.
5
+ */
6
+
7
+ /**
8
+ * Bucket interface for blob storage operations.
9
+ */
10
+ export interface Bucket {
11
+ /**
12
+ * Store a blob in the bucket.
13
+ * @param key - Unique identifier for the blob
14
+ * @param data - Blob data to store
15
+ */
16
+ put(key: string, data: Blob): Promise<void>;
17
+
18
+ /**
19
+ * Retrieve a blob from the bucket.
20
+ * @param key - Blob identifier
21
+ * @returns The stored blob or null if not found
22
+ */
23
+ get(key: string): Promise<Blob | null>;
24
+
25
+ /**
26
+ * Remove a blob from the bucket.
27
+ * @param key - Blob identifier
28
+ */
29
+ remove(key: string): Promise<void>;
30
+
31
+ /**
32
+ * List all keys in the bucket.
33
+ * @returns Array of blob keys
34
+ */
35
+ keys(): Promise<string[]>;
36
+ }
37
+
38
+ /**
39
+ * IndexedDB-based bucket implementation.
40
+ * Used as fallback when Storage Buckets API is unavailable.
41
+ */
42
+ class IndexedDBBucket implements Bucket {
43
+ private dbPromise: Promise<IDBDatabase> | null = null;
44
+ private readonly storeName = 'blobs';
45
+
46
+ constructor(private readonly bucketName: string) {}
47
+
48
+ private openDB(): Promise<IDBDatabase> {
49
+ if (this.dbPromise) return this.dbPromise;
50
+
51
+ const dbName = `bquery-bucket-${this.bucketName}`;
52
+ this.dbPromise = new Promise((resolve, reject) => {
53
+ const request = indexedDB.open(dbName, 1);
54
+
55
+ request.onupgradeneeded = () => {
56
+ const db = request.result;
57
+ if (!db.objectStoreNames.contains(this.storeName)) {
58
+ db.createObjectStore(this.storeName);
59
+ }
60
+ };
61
+
62
+ request.onsuccess = () => resolve(request.result);
63
+ request.onerror = () => reject(request.error);
64
+ });
65
+
66
+ return this.dbPromise;
67
+ }
68
+
69
+ private async withStore<T>(
70
+ mode: IDBTransactionMode,
71
+ operation: (store: IDBObjectStore) => IDBRequest<T>
72
+ ): Promise<T> {
73
+ const db = await this.openDB();
74
+ return new Promise((resolve, reject) => {
75
+ const tx = db.transaction(this.storeName, mode);
76
+ const store = tx.objectStore(this.storeName);
77
+ const request = operation(store);
78
+ request.onsuccess = () => resolve(request.result);
79
+ request.onerror = () => reject(request.error);
80
+ });
81
+ }
82
+
83
+ async put(key: string, data: Blob): Promise<void> {
84
+ await this.withStore('readwrite', (store) => store.put(data, key));
85
+ }
86
+
87
+ async get(key: string): Promise<Blob | null> {
88
+ const result = await this.withStore<Blob | undefined>('readonly', (store) => store.get(key));
89
+ return result ?? null;
90
+ }
91
+
92
+ async remove(key: string): Promise<void> {
93
+ await this.withStore('readwrite', (store) => store.delete(key));
94
+ }
95
+
96
+ async keys(): Promise<string[]> {
97
+ const result = await this.withStore<IDBValidKey[]>('readonly', (store) => store.getAllKeys());
98
+ return result.map((key) => String(key));
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Bucket manager for creating and accessing storage buckets.
104
+ */
105
+ export const buckets = {
106
+ /**
107
+ * Open or create a storage bucket.
108
+ * @param name - Bucket name
109
+ * @returns Bucket instance for blob operations
110
+ */
111
+ async open(name: string): Promise<Bucket> {
112
+ // Storage Buckets API is experimental; use IndexedDB fallback
113
+ return new IndexedDBBucket(name);
114
+ },
115
+ };
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Cache Storage API wrapper.
3
+ * Provides a simplified interface for caching responses and assets.
4
+ */
5
+
6
+ /**
7
+ * Cache handle interface for managing cached resources.
8
+ */
9
+ export interface CacheHandle {
10
+ /**
11
+ * Add a resource to the cache by URL.
12
+ * Fetches the resource and stores the response.
13
+ * @param url - URL to fetch and cache
14
+ */
15
+ add(url: string): Promise<void>;
16
+
17
+ /**
18
+ * Add multiple resources to the cache.
19
+ * @param urls - Array of URLs to fetch and cache
20
+ */
21
+ addAll(urls: string[]): Promise<void>;
22
+
23
+ /**
24
+ * Store a custom response in the cache.
25
+ * @param url - URL key for the cached response
26
+ * @param response - Response object to cache
27
+ */
28
+ put(url: string, response: Response): Promise<void>;
29
+
30
+ /**
31
+ * Retrieve a cached response.
32
+ * @param url - URL to look up
33
+ * @returns Cached Response or undefined if not found
34
+ */
35
+ match(url: string): Promise<Response | undefined>;
36
+
37
+ /**
38
+ * Remove a cached response.
39
+ * @param url - URL to remove from cache
40
+ * @returns True if the entry was deleted
41
+ */
42
+ remove(url: string): Promise<boolean>;
43
+
44
+ /**
45
+ * Get all cached request URLs.
46
+ * @returns Array of cached URLs
47
+ */
48
+ keys(): Promise<string[]>;
49
+ }
50
+
51
+ /**
52
+ * Internal cache handle implementation.
53
+ */
54
+ class CacheHandleImpl implements CacheHandle {
55
+ constructor(private readonly cache: Cache) {}
56
+
57
+ async add(url: string): Promise<void> {
58
+ await this.cache.add(url);
59
+ }
60
+
61
+ async addAll(urls: string[]): Promise<void> {
62
+ await this.cache.addAll(urls);
63
+ }
64
+
65
+ async put(url: string, response: Response): Promise<void> {
66
+ await this.cache.put(url, response);
67
+ }
68
+
69
+ async match(url: string): Promise<Response | undefined> {
70
+ return this.cache.match(url);
71
+ }
72
+
73
+ async remove(url: string): Promise<boolean> {
74
+ return this.cache.delete(url);
75
+ }
76
+
77
+ async keys(): Promise<string[]> {
78
+ const requests = await this.cache.keys();
79
+ return requests.map((req) => req.url);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Cache manager for accessing the Cache Storage API.
85
+ */
86
+ export const cache = {
87
+ /**
88
+ * Check if Cache Storage API is supported.
89
+ * @returns True if caches API is available
90
+ */
91
+ isSupported(): boolean {
92
+ return 'caches' in window;
93
+ },
94
+
95
+ /**
96
+ * Open or create a named cache.
97
+ * @param name - Cache name
98
+ * @returns CacheHandle for cache operations
99
+ */
100
+ async open(name: string): Promise<CacheHandle> {
101
+ if (!this.isSupported()) {
102
+ throw new Error('bQuery: Cache Storage API not supported');
103
+ }
104
+ const c = await caches.open(name);
105
+ return new CacheHandleImpl(c);
106
+ },
107
+
108
+ /**
109
+ * Delete a named cache.
110
+ * @param name - Cache name to delete
111
+ * @returns True if the cache was deleted
112
+ */
113
+ async delete(name: string): Promise<boolean> {
114
+ if (!this.isSupported()) {
115
+ return false;
116
+ }
117
+ return caches.delete(name);
118
+ },
119
+
120
+ /**
121
+ * List all cache names.
122
+ * @returns Array of cache names
123
+ */
124
+ async keys(): Promise<string[]> {
125
+ if (!this.isSupported()) {
126
+ return [];
127
+ }
128
+ return caches.keys();
129
+ },
130
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Platform module providing unified endpoints for web platform APIs.
3
+ * Offers consistent, promise-based interfaces with predictable errors.
4
+ *
5
+ * @module bquery/platform
6
+ */
7
+
8
+ export { buckets } from './buckets';
9
+ export type { Bucket } from './buckets';
10
+
11
+ export { cache } from './cache';
12
+ export type { CacheHandle } from './cache';
13
+
14
+ export { notifications } from './notifications';
15
+ export type { NotificationOptions } from './notifications';
16
+
17
+ export { storage } from './storage';
18
+ export type { IndexedDBOptions, StorageAdapter } from './storage';
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Web Notifications API wrapper.
3
+ * Provides a simplified interface for browser notifications.
4
+ */
5
+
6
+ /**
7
+ * Notification options matching the standard NotificationOptions interface.
8
+ */
9
+ export interface NotificationOptions {
10
+ /** Body text of the notification */
11
+ body?: string;
12
+ /** Icon URL for the notification */
13
+ icon?: string;
14
+ /** Badge icon for mobile devices */
15
+ badge?: string;
16
+ /** Tag for grouping notifications */
17
+ tag?: string;
18
+ /** Whether to require user interaction */
19
+ requireInteraction?: boolean;
20
+ /** Vibration pattern for mobile devices */
21
+ vibrate?: number[];
22
+ /** Additional data attached to the notification */
23
+ data?: unknown;
24
+ }
25
+
26
+ /**
27
+ * Notifications manager providing a clean interface for web notifications.
28
+ */
29
+ export const notifications = {
30
+ /**
31
+ * Check if notifications are supported.
32
+ * @returns True if Notification API is available
33
+ */
34
+ isSupported(): boolean {
35
+ return 'Notification' in window;
36
+ },
37
+
38
+ /**
39
+ * Get current permission status.
40
+ * @returns Current permission state
41
+ */
42
+ getPermission(): NotificationPermission {
43
+ if (!this.isSupported()) return 'denied';
44
+ return Notification.permission;
45
+ },
46
+
47
+ /**
48
+ * Request notification permission from the user.
49
+ * @returns Promise resolving to the permission result
50
+ */
51
+ async requestPermission(): Promise<NotificationPermission> {
52
+ if (!this.isSupported()) {
53
+ return 'denied';
54
+ }
55
+
56
+ if (Notification.permission === 'granted') {
57
+ return 'granted';
58
+ }
59
+
60
+ if (Notification.permission === 'denied') {
61
+ return 'denied';
62
+ }
63
+
64
+ return Notification.requestPermission();
65
+ },
66
+
67
+ /**
68
+ * Send a notification.
69
+ * Requires 'granted' permission.
70
+ * @param title - Notification title
71
+ * @param options - Optional notification settings
72
+ * @returns The Notification instance or null if not permitted
73
+ */
74
+ send(title: string, options?: NotificationOptions): Notification | null {
75
+ if (!this.isSupported()) {
76
+ console.warn('bQuery: Notifications not supported in this browser');
77
+ return null;
78
+ }
79
+
80
+ if (Notification.permission !== 'granted') {
81
+ console.warn('bQuery: Notification permission not granted');
82
+ return null;
83
+ }
84
+
85
+ return new Notification(title, options);
86
+ },
87
+ };