@codingfactory/notify-kit-client 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/dist/index.js ADDED
@@ -0,0 +1,737 @@
1
+ import { useNotifyKitStore } from './chunk-7LQBNDRD.js';
2
+ export { InvalidSnoozeDurationError, NOTIFY_KIT_MODALS_KEY, NOTIFY_KIT_STORE_KEY, provideNotifyKitModals, provideNotifyKitStore, resetNotifyKitStore, useNotifyKitModals, useNotifyKitStore } from './chunk-7LQBNDRD.js';
3
+ import { provide, inject, ref, computed, onMounted, onUnmounted, watch } from 'vue';
4
+
5
+ // src/types/notification.ts
6
+ var NOTIFICATION_CATEGORIES = [
7
+ "workspace",
8
+ "security",
9
+ "orders",
10
+ "social",
11
+ "system",
12
+ "marketing"
13
+ ];
14
+ var NOTIFICATION_LEVELS = [
15
+ "info",
16
+ "success",
17
+ "warning",
18
+ "danger"
19
+ ];
20
+ function isValidCategory(value) {
21
+ return typeof value === "string" && NOTIFICATION_CATEGORIES.includes(value);
22
+ }
23
+ function isValidLevel(value) {
24
+ return typeof value === "string" && NOTIFICATION_LEVELS.includes(value);
25
+ }
26
+ function isNotifyKitNotification(value) {
27
+ if (typeof value !== "object" || value === null) {
28
+ return false;
29
+ }
30
+ const obj = value;
31
+ if (typeof obj["id"] !== "string") return false;
32
+ if (typeof obj["key"] !== "string") return false;
33
+ if (typeof obj["title"] !== "string") return false;
34
+ if (typeof obj["body"] !== "string") return false;
35
+ if (typeof obj["created_at"] !== "string") return false;
36
+ if (!isValidCategory(obj["category"])) return false;
37
+ if (!isValidLevel(obj["level"])) return false;
38
+ if (obj["action_url"] !== null && typeof obj["action_url"] !== "string") return false;
39
+ if (obj["workspace_id"] !== null && typeof obj["workspace_id"] !== "string") return false;
40
+ if (obj["group_key"] !== null && typeof obj["group_key"] !== "string") return false;
41
+ if (obj["idempotency_key"] !== null && typeof obj["idempotency_key"] !== "string")
42
+ return false;
43
+ if (obj["read_at"] !== null && typeof obj["read_at"] !== "string") return false;
44
+ if (typeof obj["data"] !== "object" || obj["data"] === null) {
45
+ if (obj["data"] !== void 0 && (typeof obj["data"] !== "object" || obj["data"] === null)) {
46
+ return false;
47
+ }
48
+ }
49
+ if (obj["modal"] !== null && typeof obj["modal"] !== "object") return false;
50
+ return true;
51
+ }
52
+ function isModalEnabled(notification) {
53
+ return notification.modal?.enabled ?? false;
54
+ }
55
+
56
+ // src/types/modal.ts
57
+ function isModalSpec(value) {
58
+ if (typeof value !== "object" || value === null) {
59
+ return false;
60
+ }
61
+ const obj = value;
62
+ if (typeof obj["enabled"] !== "boolean") return false;
63
+ if (typeof obj["requires_ack"] !== "boolean") return false;
64
+ if (!Array.isArray(obj["snooze_options_minutes"])) return false;
65
+ const snoozeOptions = obj["snooze_options_minutes"];
66
+ for (const option of snoozeOptions) {
67
+ if (typeof option !== "number" || !Number.isInteger(option) || option < 1) {
68
+ return false;
69
+ }
70
+ }
71
+ return true;
72
+ }
73
+
74
+ // src/types/preferences.ts
75
+ function isChannelPreferences(value) {
76
+ if (typeof value !== "object" || value === null) {
77
+ return false;
78
+ }
79
+ const obj = value;
80
+ return typeof obj["database"] === "boolean" && typeof obj["broadcast"] === "boolean" && typeof obj["mail"] === "boolean";
81
+ }
82
+
83
+ // src/types/api.ts
84
+ function isPaginationMeta(value) {
85
+ if (typeof value !== "object" || value === null) {
86
+ return false;
87
+ }
88
+ const obj = value;
89
+ return typeof obj["current_page"] === "number" && typeof obj["last_page"] === "number" && typeof obj["per_page"] === "number" && typeof obj["total"] === "number";
90
+ }
91
+ function isMeResponse(value) {
92
+ if (typeof value !== "object" || value === null) {
93
+ return false;
94
+ }
95
+ const obj = value;
96
+ return typeof obj["broadcast_channel"] === "string";
97
+ }
98
+ function isUnreadCountResponse(value) {
99
+ if (typeof value !== "object" || value === null) {
100
+ return false;
101
+ }
102
+ const obj = value;
103
+ return typeof obj["count"] === "number";
104
+ }
105
+
106
+ // src/client/NotifyKitClient.ts
107
+ var NotifyKitApiError = class extends Error {
108
+ constructor(message, status, errors) {
109
+ super(message);
110
+ this.status = status;
111
+ this.errors = errors;
112
+ }
113
+ name = "NotifyKitApiError";
114
+ };
115
+ var NotifyKitClient = class {
116
+ baseUrl;
117
+ getAuthHeaders;
118
+ fetchFn;
119
+ constructor(config) {
120
+ this.baseUrl = config.baseUrl.replace(/\/+$/, "");
121
+ this.getAuthHeaders = config.getAuthHeaders ?? void 0;
122
+ this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
123
+ }
124
+ // ============================================
125
+ // Me
126
+ // ============================================
127
+ /**
128
+ * Get the current user's broadcast channel information.
129
+ */
130
+ async getMe() {
131
+ return this.request("GET", "/me");
132
+ }
133
+ // ============================================
134
+ // Notifications
135
+ // ============================================
136
+ /**
137
+ * List notifications for the current user.
138
+ */
139
+ async listNotifications(params) {
140
+ const queryParams = this.buildNotificationParams(params);
141
+ const queryString = queryParams.toString();
142
+ const url = queryString.length > 0 ? `/notifications?${queryString}` : "/notifications";
143
+ return this.request("GET", url);
144
+ }
145
+ /**
146
+ * Get the count of unread notifications.
147
+ */
148
+ async getUnreadCount() {
149
+ return this.request(
150
+ "GET",
151
+ "/notifications/unread-count"
152
+ );
153
+ }
154
+ /**
155
+ * Mark a notification as read.
156
+ */
157
+ async markRead(id) {
158
+ await this.request("PATCH", `/notifications/${id}/read`);
159
+ }
160
+ /**
161
+ * Mark all notifications as read.
162
+ */
163
+ async markAllRead() {
164
+ await this.request("POST", "/notifications/read-all");
165
+ }
166
+ /**
167
+ * Delete a notification.
168
+ */
169
+ async deleteNotification(id) {
170
+ await this.request("DELETE", `/notifications/${id}`);
171
+ }
172
+ // ============================================
173
+ // Groups (Phase 2)
174
+ // ============================================
175
+ /**
176
+ * Mark all notifications in a group as read.
177
+ */
178
+ async markGroupRead(groupKey) {
179
+ await this.request("PATCH", `/notifications/groups/${groupKey}/read`);
180
+ }
181
+ /**
182
+ * Get all notifications in a group.
183
+ */
184
+ async getGroupNotifications(groupKey, params) {
185
+ const queryParams = new URLSearchParams();
186
+ if (params?.page !== void 0) {
187
+ queryParams.set("page", String(params.page));
188
+ }
189
+ if (params?.per_page !== void 0) {
190
+ queryParams.set("per_page", String(params.per_page));
191
+ }
192
+ const queryString = queryParams.toString();
193
+ const url = queryString.length > 0 ? `/notifications/groups/${groupKey}?${queryString}` : `/notifications/groups/${groupKey}`;
194
+ return this.request("GET", url);
195
+ }
196
+ // ============================================
197
+ // Modals
198
+ // ============================================
199
+ /**
200
+ * List outstanding modal notifications.
201
+ */
202
+ async listModals() {
203
+ const response = await this.request(
204
+ "GET",
205
+ "/modals"
206
+ );
207
+ return response.data;
208
+ }
209
+ /**
210
+ * Acknowledge a modal notification.
211
+ */
212
+ async ackModal(id) {
213
+ await this.request("POST", `/modals/${id}/ack`);
214
+ }
215
+ /**
216
+ * Snooze a modal notification.
217
+ */
218
+ async snoozeModal(id, minutes) {
219
+ await this.request("POST", `/modals/${id}/snooze`, { minutes });
220
+ }
221
+ // ============================================
222
+ // Preferences
223
+ // ============================================
224
+ /**
225
+ * Get the current user's notification preferences.
226
+ */
227
+ async getPreferences() {
228
+ return this.request("GET", "/preferences");
229
+ }
230
+ /**
231
+ * Update the current user's notification preferences.
232
+ */
233
+ async updatePreferences(preferences) {
234
+ await this.request("PUT", "/preferences", preferences);
235
+ }
236
+ // ============================================
237
+ // Private helpers
238
+ // ============================================
239
+ buildNotificationParams(params) {
240
+ const queryParams = new URLSearchParams();
241
+ if (params === void 0) {
242
+ return queryParams;
243
+ }
244
+ if (params.unread === true) {
245
+ queryParams.set("unread", "true");
246
+ }
247
+ if (params.category !== void 0) {
248
+ queryParams.set("category", params.category);
249
+ }
250
+ if (params.workspace_id !== void 0) {
251
+ queryParams.set("workspace_id", params.workspace_id);
252
+ }
253
+ if (params.grouped === true) {
254
+ queryParams.set("grouped", "1");
255
+ }
256
+ if (params.page !== void 0) {
257
+ queryParams.set("page", String(params.page));
258
+ }
259
+ if (params.per_page !== void 0) {
260
+ queryParams.set("per_page", String(params.per_page));
261
+ }
262
+ return queryParams;
263
+ }
264
+ async request(method, path, body) {
265
+ const url = `${this.baseUrl}${path}`;
266
+ const headers = {
267
+ "Content-Type": "application/json",
268
+ Accept: "application/json"
269
+ };
270
+ if (this.getAuthHeaders !== void 0) {
271
+ const authHeaders = await this.getAuthHeaders();
272
+ Object.assign(headers, authHeaders);
273
+ }
274
+ const requestInit = {
275
+ method,
276
+ headers
277
+ };
278
+ if (body !== void 0) {
279
+ requestInit.body = JSON.stringify(body);
280
+ }
281
+ const response = await this.fetchFn(url, requestInit);
282
+ if (!response.ok) {
283
+ const errorData = await response.json();
284
+ throw new NotifyKitApiError(
285
+ errorData.message ?? `Request failed with status ${String(response.status)}`,
286
+ response.status,
287
+ errorData.errors
288
+ );
289
+ }
290
+ const text = await response.text();
291
+ if (text.length === 0) {
292
+ return void 0;
293
+ }
294
+ return JSON.parse(text);
295
+ }
296
+ };
297
+ function createNotifyKitClient(config) {
298
+ return new NotifyKitClient(config);
299
+ }
300
+
301
+ // src/client/endpoints.ts
302
+ var ENDPOINTS = {
303
+ /** GET - Returns broadcast channel for authenticated user */
304
+ ME: "/me",
305
+ /** GET - List notifications (supports filters and pagination) */
306
+ NOTIFICATIONS: "/notifications",
307
+ /** GET - Get count of unread notifications */
308
+ UNREAD_COUNT: "/notifications/unread-count",
309
+ /** PATCH - Mark a specific notification as read */
310
+ MARK_READ: (id) => `/notifications/${id}/read`,
311
+ /** POST - Mark all notifications as read */
312
+ MARK_ALL_READ: "/notifications/read-all",
313
+ /** DELETE - Delete a specific notification */
314
+ DELETE_NOTIFICATION: (id) => `/notifications/${id}`,
315
+ /** PATCH - Mark all notifications in a group as read (Phase 2) */
316
+ MARK_GROUP_READ: (groupKey) => `/notifications/groups/${groupKey}/read`,
317
+ /** GET - Get all notifications in a group (Phase 2) */
318
+ GROUP_NOTIFICATIONS: (groupKey) => `/notifications/groups/${groupKey}`,
319
+ /** GET - List outstanding modal notifications */
320
+ MODALS: "/modals",
321
+ /** POST - Acknowledge a modal notification */
322
+ ACK_MODAL: (id) => `/modals/${id}/ack`,
323
+ /** POST - Snooze a modal notification */
324
+ SNOOZE_MODAL: (id) => `/modals/${id}/snooze`,
325
+ /** GET - Get user's notification preferences */
326
+ PREFERENCES: "/preferences"
327
+ };
328
+ var NOTIFY_KIT_CLIENT_KEY = /* @__PURE__ */ Symbol(
329
+ "notify-kit-client"
330
+ );
331
+ function provideNotifyKitClient(config) {
332
+ const client = createNotifyKitClient(config);
333
+ provide(NOTIFY_KIT_CLIENT_KEY, client);
334
+ return client;
335
+ }
336
+ function useNotifyKitClient(config) {
337
+ let client;
338
+ if (config !== void 0) {
339
+ client = createNotifyKitClient(config);
340
+ } else {
341
+ const injectedClient = inject(NOTIFY_KIT_CLIENT_KEY);
342
+ if (injectedClient === void 0) {
343
+ throw new Error(
344
+ "No NotifyKit client available. Either provide config to useNotifyKitClient() or call provideNotifyKitClient() in a parent component."
345
+ );
346
+ }
347
+ client = injectedClient;
348
+ }
349
+ return {
350
+ client,
351
+ // Me
352
+ getMe: () => client.getMe(),
353
+ // Notifications
354
+ listNotifications: (params) => client.listNotifications(params),
355
+ getUnreadCount: () => client.getUnreadCount(),
356
+ markRead: (id) => client.markRead(id),
357
+ markAllRead: () => client.markAllRead(),
358
+ deleteNotification: (id) => client.deleteNotification(id),
359
+ // Groups
360
+ markGroupRead: (groupKey) => client.markGroupRead(groupKey),
361
+ getGroupNotifications: (groupKey, params) => client.getGroupNotifications(groupKey, params),
362
+ // Modals
363
+ listModals: () => client.listModals(),
364
+ ackModal: (id) => client.ackModal(id),
365
+ snoozeModal: (id, minutes) => client.snoozeModal(id, minutes),
366
+ // Preferences
367
+ getPreferences: () => client.getPreferences(),
368
+ updatePreferences: (preferences) => client.updatePreferences(preferences)
369
+ };
370
+ }
371
+ function useNotifyKitRealtime(options) {
372
+ const {
373
+ echo: echoOption,
374
+ client,
375
+ autoConnect = true,
376
+ onNotification,
377
+ onConnectionChange
378
+ } = options;
379
+ const store = useNotifyKitStore();
380
+ const connectionState = ref("disconnected");
381
+ const currentChannel = ref(null);
382
+ const isSubscribed = ref(false);
383
+ const isConnected = computed(() => connectionState.value === "connected");
384
+ function getEcho() {
385
+ return typeof echoOption === "function" ? echoOption() : echoOption;
386
+ }
387
+ function setConnectionState(state) {
388
+ connectionState.value = state;
389
+ store.setConnectionState(state);
390
+ onConnectionChange?.(state);
391
+ }
392
+ function handleNotification(payload) {
393
+ if (!isNotifyKitNotification(payload)) {
394
+ return;
395
+ }
396
+ store.addNotification(payload);
397
+ store.incrementUnreadCount();
398
+ if (isModalEnabled(payload)) {
399
+ store.enqueueModal(payload);
400
+ }
401
+ onNotification?.(payload);
402
+ }
403
+ async function connect() {
404
+ if (connectionState.value === "connected" || connectionState.value === "connecting") {
405
+ return;
406
+ }
407
+ setConnectionState("connecting");
408
+ try {
409
+ const meResponse = await client.getMe();
410
+ const channel = meResponse.broadcast_channel;
411
+ currentChannel.value = channel;
412
+ store.setBroadcastChannel(channel);
413
+ const echo = getEcho();
414
+ echo.private(channel).notification(handleNotification);
415
+ isSubscribed.value = true;
416
+ setConnectionState("connected");
417
+ } catch (error) {
418
+ setConnectionState("error");
419
+ throw error;
420
+ }
421
+ }
422
+ function disconnect() {
423
+ if (currentChannel.value !== null && isSubscribed.value) {
424
+ const echo = getEcho();
425
+ echo.leave(currentChannel.value);
426
+ isSubscribed.value = false;
427
+ }
428
+ setConnectionState("disconnected");
429
+ }
430
+ async function reconnect() {
431
+ disconnect();
432
+ await connect();
433
+ }
434
+ onMounted(() => {
435
+ if (autoConnect) {
436
+ void connect();
437
+ }
438
+ });
439
+ onUnmounted(() => {
440
+ if (isConnected.value) {
441
+ disconnect();
442
+ }
443
+ });
444
+ return {
445
+ isConnected,
446
+ connectionState,
447
+ connect,
448
+ disconnect,
449
+ reconnect
450
+ };
451
+ }
452
+ var DEFAULT_CONFIG = {
453
+ disconnectThresholdMs: 5e3,
454
+ unreadCountIntervalMs: 3e4,
455
+ modalsIntervalMs: 12e4,
456
+ reconnectGracePeriodMs: 2e3
457
+ };
458
+ function useNotifyKitFallback(options) {
459
+ const { client, connectionState, config: userConfig, onModalsReceived, onUnreadCountReceived } = options;
460
+ const config = {
461
+ ...DEFAULT_CONFIG,
462
+ ...userConfig
463
+ };
464
+ const store = useNotifyKitStore();
465
+ const isPolling = ref(false);
466
+ const lastPollAt = ref(null);
467
+ let disconnectTimer = null;
468
+ let unreadCountInterval = null;
469
+ let modalsInterval = null;
470
+ let reconnectGraceTimer = null;
471
+ async function pollUnreadCount() {
472
+ try {
473
+ const response = await client.getUnreadCount();
474
+ store.setUnreadCount(response.count);
475
+ lastPollAt.value = Date.now();
476
+ onUnreadCountReceived?.(response.count);
477
+ } catch {
478
+ }
479
+ }
480
+ async function pollModals() {
481
+ try {
482
+ const modals = await client.listModals();
483
+ for (const modal of modals) {
484
+ store.enqueueModal(modal);
485
+ }
486
+ store.setLastModalsSync(Date.now());
487
+ lastPollAt.value = Date.now();
488
+ onModalsReceived?.(modals);
489
+ } catch {
490
+ }
491
+ }
492
+ function startPolling() {
493
+ if (isPolling.value) {
494
+ return;
495
+ }
496
+ isPolling.value = true;
497
+ void pollUnreadCount();
498
+ void pollModals();
499
+ unreadCountInterval = setInterval(() => {
500
+ void pollUnreadCount();
501
+ }, config.unreadCountIntervalMs);
502
+ modalsInterval = setInterval(() => {
503
+ void pollModals();
504
+ }, config.modalsIntervalMs);
505
+ }
506
+ function stopPolling() {
507
+ if (!isPolling.value) {
508
+ return;
509
+ }
510
+ isPolling.value = false;
511
+ if (unreadCountInterval !== null) {
512
+ clearInterval(unreadCountInterval);
513
+ unreadCountInterval = null;
514
+ }
515
+ if (modalsInterval !== null) {
516
+ clearInterval(modalsInterval);
517
+ modalsInterval = null;
518
+ }
519
+ }
520
+ function clearAllTimers() {
521
+ if (disconnectTimer !== null) {
522
+ clearTimeout(disconnectTimer);
523
+ disconnectTimer = null;
524
+ }
525
+ if (reconnectGraceTimer !== null) {
526
+ clearTimeout(reconnectGraceTimer);
527
+ reconnectGraceTimer = null;
528
+ }
529
+ }
530
+ function handleConnectionStateChange(state) {
531
+ if (state === "disconnected" || state === "error") {
532
+ if (reconnectGraceTimer !== null) {
533
+ clearTimeout(reconnectGraceTimer);
534
+ reconnectGraceTimer = null;
535
+ }
536
+ disconnectTimer ??= setTimeout(() => {
537
+ startPolling();
538
+ disconnectTimer = null;
539
+ }, config.disconnectThresholdMs);
540
+ } else if (state === "connected") {
541
+ if (disconnectTimer !== null) {
542
+ clearTimeout(disconnectTimer);
543
+ disconnectTimer = null;
544
+ }
545
+ if (isPolling.value) {
546
+ reconnectGraceTimer = setTimeout(() => {
547
+ stopPolling();
548
+ void pollUnreadCount();
549
+ void pollModals();
550
+ reconnectGraceTimer = null;
551
+ }, config.reconnectGracePeriodMs);
552
+ }
553
+ }
554
+ }
555
+ watch(connectionState, handleConnectionStateChange, { immediate: true });
556
+ onUnmounted(() => {
557
+ clearAllTimers();
558
+ stopPolling();
559
+ });
560
+ return {
561
+ isPolling,
562
+ lastPollAt,
563
+ startPolling,
564
+ stopPolling,
565
+ pollUnreadCount,
566
+ pollModals
567
+ };
568
+ }
569
+
570
+ // src/helpers/toastPolicy.ts
571
+ var DEFAULT_TOAST_CATEGORIES = {
572
+ workspace: true,
573
+ security: true,
574
+ orders: true,
575
+ social: false,
576
+ system: true,
577
+ marketing: true
578
+ };
579
+ var DEFAULT_SOUND_LEVELS = ["danger"];
580
+ var DEFAULT_SOUND_CATEGORIES = ["security"];
581
+ function isCriticalModal(notification) {
582
+ return notification.modal !== null && notification.modal.enabled && notification.modal.requires_ack;
583
+ }
584
+ function defaultShouldToast(notification, categorySettings) {
585
+ if (isCriticalModal(notification)) {
586
+ return false;
587
+ }
588
+ return categorySettings[notification.category];
589
+ }
590
+ function defaultShouldPlaySound(notification, soundLevels, soundCategories) {
591
+ if (soundLevels.includes(notification.level)) {
592
+ return true;
593
+ }
594
+ if (soundCategories.includes(notification.category)) {
595
+ return true;
596
+ }
597
+ return false;
598
+ }
599
+ var defaultToastPolicy = {
600
+ shouldToast(notification) {
601
+ return defaultShouldToast(notification, DEFAULT_TOAST_CATEGORIES);
602
+ },
603
+ shouldPlaySound(notification) {
604
+ return defaultShouldPlaySound(
605
+ notification,
606
+ DEFAULT_SOUND_LEVELS,
607
+ DEFAULT_SOUND_CATEGORIES
608
+ );
609
+ }
610
+ };
611
+ function createToastPolicy(config) {
612
+ const categorySettings = {
613
+ ...DEFAULT_TOAST_CATEGORIES,
614
+ ...config.toastCategories
615
+ };
616
+ const soundLevels = config.soundLevels ?? DEFAULT_SOUND_LEVELS;
617
+ const soundCategories = config.soundCategories ?? DEFAULT_SOUND_CATEGORIES;
618
+ return {
619
+ shouldToast(notification) {
620
+ if (config.shouldToast !== void 0) {
621
+ return config.shouldToast(notification);
622
+ }
623
+ return defaultShouldToast(notification, categorySettings);
624
+ },
625
+ shouldPlaySound(notification) {
626
+ if (config.shouldPlaySound !== void 0) {
627
+ return config.shouldPlaySound(notification);
628
+ }
629
+ return defaultShouldPlaySound(notification, soundLevels, soundCategories);
630
+ }
631
+ };
632
+ }
633
+
634
+ // src/helpers/modalHelpers.ts
635
+ function getModalAriaAttributes(notification, ids) {
636
+ const ariaRole = notification.modal?.accessibility?.aria_role ?? "alertdialog";
637
+ return {
638
+ role: ariaRole,
639
+ "aria-modal": "true",
640
+ "aria-labelledby": ids.titleId,
641
+ "aria-describedby": ids.bodyId
642
+ };
643
+ }
644
+ function getEscapeKeyHandler(notification, callbacks) {
645
+ const behavior = notification.modal?.accessibility?.escape_key_behavior ?? "close";
646
+ if (behavior === "disabled") {
647
+ return null;
648
+ }
649
+ return (event) => {
650
+ if (event.key !== "Escape") return;
651
+ switch (behavior) {
652
+ case "close":
653
+ event.preventDefault();
654
+ callbacks.onClose();
655
+ break;
656
+ case "snooze": {
657
+ event.preventDefault();
658
+ const minSnooze = notification.modal?.snooze_options_minutes[0] ?? 15;
659
+ callbacks.onSnooze(minSnooze);
660
+ break;
661
+ }
662
+ }
663
+ };
664
+ }
665
+ function createFocusTrap(modalElement) {
666
+ const focusableSelector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
667
+ let previousFocus = null;
668
+ let trapHandler = null;
669
+ function trapFocus(event) {
670
+ if (event.key !== "Tab") return;
671
+ const focusable = modalElement.querySelectorAll(focusableSelector);
672
+ if (focusable.length === 0) return;
673
+ const first = focusable[0];
674
+ const last = focusable[focusable.length - 1];
675
+ if (first === void 0 || last === void 0) return;
676
+ if (event.shiftKey && document.activeElement === first) {
677
+ event.preventDefault();
678
+ last.focus();
679
+ } else if (!event.shiftKey && document.activeElement === last) {
680
+ event.preventDefault();
681
+ first.focus();
682
+ }
683
+ }
684
+ return {
685
+ activate() {
686
+ previousFocus = document.activeElement;
687
+ const focusable = modalElement.querySelectorAll(focusableSelector);
688
+ if (focusable.length > 0 && focusable[0] !== void 0) {
689
+ focusable[0].focus();
690
+ }
691
+ trapHandler = trapFocus;
692
+ modalElement.addEventListener("keydown", trapHandler);
693
+ },
694
+ deactivate() {
695
+ if (trapHandler !== null) {
696
+ modalElement.removeEventListener("keydown", trapHandler);
697
+ trapHandler = null;
698
+ }
699
+ if (previousFocus !== null) {
700
+ previousFocus.focus();
701
+ previousFocus = null;
702
+ }
703
+ }
704
+ };
705
+ }
706
+ function getActionButtonAttributes(action, index) {
707
+ const attrs = {
708
+ type: "button",
709
+ "data-action-type": action.type,
710
+ "data-action-index": String(index),
711
+ "aria-label": action.label
712
+ };
713
+ if (action.primary === true) {
714
+ attrs["data-primary"] = true;
715
+ }
716
+ return attrs;
717
+ }
718
+ function isCriticalModal2(notification) {
719
+ return notification.modal?.enabled === true && notification.modal.requires_ack === true;
720
+ }
721
+ function shouldCloseOnBackdropClick(notification) {
722
+ return !isCriticalModal2(notification);
723
+ }
724
+ function getMinSnoozeDuration(notification) {
725
+ const options = notification.modal?.snooze_options_minutes;
726
+ if (options === void 0 || options.length === 0) {
727
+ return null;
728
+ }
729
+ return options[0] ?? null;
730
+ }
731
+
732
+ // src/index.ts
733
+ var VERSION = "0.1.0";
734
+
735
+ export { ENDPOINTS, NOTIFICATION_CATEGORIES, NOTIFICATION_LEVELS, NOTIFY_KIT_CLIENT_KEY, NotifyKitApiError, NotifyKitClient, VERSION, createFocusTrap, createNotifyKitClient, createToastPolicy, defaultToastPolicy, getActionButtonAttributes, getEscapeKeyHandler, getMinSnoozeDuration, getModalAriaAttributes, isChannelPreferences, isCriticalModal2 as isCriticalModal, isMeResponse, isModalEnabled, isModalSpec, isNotifyKitNotification, isPaginationMeta, isUnreadCountResponse, isValidCategory, isValidLevel, provideNotifyKitClient, shouldCloseOnBackdropClick, useNotifyKitClient, useNotifyKitFallback, useNotifyKitRealtime };
736
+ //# sourceMappingURL=index.js.map
737
+ //# sourceMappingURL=index.js.map