@mappoh/nova 0.3.0 → 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.
Files changed (252) hide show
  1. package/README.md +180 -30
  2. package/dist/alert/alert.d.ts +32 -0
  3. package/dist/alert/alert.d.ts.map +1 -0
  4. package/dist/alert/alert.js +307 -0
  5. package/dist/alert/alert.js.map +1 -0
  6. package/dist/alert/index.d.ts +3 -0
  7. package/dist/alert/index.d.ts.map +1 -0
  8. package/dist/alert/index.js +2 -0
  9. package/dist/alert/index.js.map +1 -0
  10. package/dist/animation/flip.d.ts.map +1 -1
  11. package/dist/animation/flip.js +19 -12
  12. package/dist/animation/flip.js.map +1 -1
  13. package/dist/animation/stagger.d.ts +43 -0
  14. package/dist/animation/stagger.d.ts.map +1 -0
  15. package/dist/animation/stagger.js +150 -0
  16. package/dist/animation/stagger.js.map +1 -0
  17. package/dist/avatar/avatar.d.ts +27 -0
  18. package/dist/avatar/avatar.d.ts.map +1 -0
  19. package/dist/avatar/avatar.js +132 -0
  20. package/dist/avatar/avatar.js.map +1 -0
  21. package/dist/avatar/index.d.ts +3 -0
  22. package/dist/avatar/index.d.ts.map +1 -0
  23. package/dist/avatar/index.js +2 -0
  24. package/dist/avatar/index.js.map +1 -0
  25. package/dist/badge/badge.d.ts +27 -0
  26. package/dist/badge/badge.d.ts.map +1 -0
  27. package/dist/badge/badge.js +118 -0
  28. package/dist/badge/badge.js.map +1 -0
  29. package/dist/badge/index.d.ts +3 -0
  30. package/dist/badge/index.d.ts.map +1 -0
  31. package/dist/badge/index.js +2 -0
  32. package/dist/badge/index.js.map +1 -0
  33. package/dist/cache/cache.d.ts +28 -0
  34. package/dist/cache/cache.d.ts.map +1 -0
  35. package/dist/cache/cache.js +67 -0
  36. package/dist/cache/cache.js.map +1 -0
  37. package/dist/cache/index.d.ts +3 -0
  38. package/dist/cache/index.d.ts.map +1 -0
  39. package/dist/cache/index.js +2 -0
  40. package/dist/cache/index.js.map +1 -0
  41. package/dist/chart/chart.d.ts +4 -0
  42. package/dist/chart/chart.d.ts.map +1 -1
  43. package/dist/chart/chart.js +89 -6
  44. package/dist/chart/chart.js.map +1 -1
  45. package/dist/command-palette/command-palette.d.ts +31 -0
  46. package/dist/command-palette/command-palette.d.ts.map +1 -0
  47. package/dist/command-palette/command-palette.js +590 -0
  48. package/dist/command-palette/command-palette.js.map +1 -0
  49. package/dist/command-palette/index.d.ts +3 -0
  50. package/dist/command-palette/index.d.ts.map +1 -0
  51. package/dist/command-palette/index.js +2 -0
  52. package/dist/command-palette/index.js.map +1 -0
  53. package/dist/component/component.d.ts +20 -2
  54. package/dist/component/component.d.ts.map +1 -1
  55. package/dist/component/component.js +115 -5
  56. package/dist/component/component.js.map +1 -1
  57. package/dist/component/connect.d.ts +50 -0
  58. package/dist/component/connect.d.ts.map +1 -1
  59. package/dist/component/connect.js +135 -0
  60. package/dist/component/connect.js.map +1 -1
  61. package/dist/component/directives.d.ts +20 -0
  62. package/dist/component/directives.d.ts.map +1 -0
  63. package/dist/component/directives.js +42 -0
  64. package/dist/component/directives.js.map +1 -0
  65. package/dist/component/html.d.ts +8 -0
  66. package/dist/component/html.d.ts.map +1 -1
  67. package/dist/component/html.js +11 -0
  68. package/dist/component/html.js.map +1 -1
  69. package/dist/component/index.d.ts +9 -3
  70. package/dist/component/index.d.ts.map +1 -1
  71. package/dist/component/index.js +6 -2
  72. package/dist/component/index.js.map +1 -1
  73. package/dist/component/portal.d.ts +32 -0
  74. package/dist/component/portal.d.ts.map +1 -0
  75. package/dist/component/portal.js +59 -0
  76. package/dist/component/portal.js.map +1 -0
  77. package/dist/component/ref.d.ts +18 -0
  78. package/dist/component/ref.d.ts.map +1 -0
  79. package/dist/component/ref.js +17 -0
  80. package/dist/component/ref.js.map +1 -0
  81. package/dist/component/slot-styles.d.ts +18 -0
  82. package/dist/component/slot-styles.d.ts.map +1 -0
  83. package/dist/component/slot-styles.js +47 -0
  84. package/dist/component/slot-styles.js.map +1 -0
  85. package/dist/component/template.d.ts +2 -0
  86. package/dist/component/template.d.ts.map +1 -1
  87. package/dist/component/template.js +122 -4
  88. package/dist/component/template.js.map +1 -1
  89. package/dist/context/context.d.ts +39 -0
  90. package/dist/context/context.d.ts.map +1 -0
  91. package/dist/context/context.js +111 -0
  92. package/dist/context/context.js.map +1 -0
  93. package/dist/context/index.d.ts +3 -0
  94. package/dist/context/index.d.ts.map +1 -0
  95. package/dist/context/index.js +2 -0
  96. package/dist/context/index.js.map +1 -0
  97. package/dist/devtools/devtools.d.ts +54 -0
  98. package/dist/devtools/devtools.d.ts.map +1 -1
  99. package/dist/devtools/devtools.js +86 -0
  100. package/dist/devtools/devtools.js.map +1 -1
  101. package/dist/devtools/index.d.ts +2 -2
  102. package/dist/devtools/index.d.ts.map +1 -1
  103. package/dist/devtools/index.js +1 -1
  104. package/dist/devtools/index.js.map +1 -1
  105. package/dist/editor/editor.d.ts +40 -0
  106. package/dist/editor/editor.d.ts.map +1 -0
  107. package/dist/editor/editor.js +955 -0
  108. package/dist/editor/editor.js.map +1 -0
  109. package/dist/editor/index.d.ts +3 -0
  110. package/dist/editor/index.d.ts.map +1 -0
  111. package/dist/editor/index.js +2 -0
  112. package/dist/editor/index.js.map +1 -0
  113. package/dist/event-bus/event-bus.d.ts +20 -0
  114. package/dist/event-bus/event-bus.d.ts.map +1 -0
  115. package/dist/event-bus/event-bus.js +55 -0
  116. package/dist/event-bus/event-bus.js.map +1 -0
  117. package/dist/event-bus/index.d.ts +3 -0
  118. package/dist/event-bus/index.d.ts.map +1 -0
  119. package/dist/event-bus/index.js +2 -0
  120. package/dist/event-bus/index.js.map +1 -0
  121. package/dist/forms/form-engine.d.ts +1 -1
  122. package/dist/forms/form-engine.d.ts.map +1 -1
  123. package/dist/forms/wasm-validators.d.ts +19 -11
  124. package/dist/forms/wasm-validators.d.ts.map +1 -1
  125. package/dist/forms/wasm-validators.js +191 -31
  126. package/dist/forms/wasm-validators.js.map +1 -1
  127. package/dist/gesture/gesture.d.ts +2 -0
  128. package/dist/gesture/gesture.d.ts.map +1 -1
  129. package/dist/gesture/gesture.js +81 -0
  130. package/dist/gesture/gesture.js.map +1 -1
  131. package/dist/http/http.d.ts +8 -0
  132. package/dist/http/http.d.ts.map +1 -1
  133. package/dist/http/http.js +18 -4
  134. package/dist/http/http.js.map +1 -1
  135. package/dist/i18n/i18n.d.ts +6 -0
  136. package/dist/i18n/i18n.d.ts.map +1 -1
  137. package/dist/i18n/i18n.js +71 -9
  138. package/dist/i18n/i18n.js.map +1 -1
  139. package/dist/machine/index.d.ts +3 -0
  140. package/dist/machine/index.d.ts.map +1 -0
  141. package/dist/machine/index.js +2 -0
  142. package/dist/machine/index.js.map +1 -0
  143. package/dist/machine/machine.d.ts +26 -0
  144. package/dist/machine/machine.d.ts.map +1 -0
  145. package/dist/machine/machine.js +79 -0
  146. package/dist/machine/machine.js.map +1 -0
  147. package/dist/modal/modal.d.ts.map +1 -1
  148. package/dist/modal/modal.js +13 -29
  149. package/dist/modal/modal.js.map +1 -1
  150. package/dist/notification-center/index.d.ts +3 -0
  151. package/dist/notification-center/index.d.ts.map +1 -0
  152. package/dist/notification-center/index.js +2 -0
  153. package/dist/notification-center/index.js.map +1 -0
  154. package/dist/notification-center/notification-center.d.ts +55 -0
  155. package/dist/notification-center/notification-center.d.ts.map +1 -0
  156. package/dist/notification-center/notification-center.js +941 -0
  157. package/dist/notification-center/notification-center.js.map +1 -0
  158. package/dist/pagination/index.d.ts +3 -0
  159. package/dist/pagination/index.d.ts.map +1 -0
  160. package/dist/pagination/index.js +2 -0
  161. package/dist/pagination/index.js.map +1 -0
  162. package/dist/pagination/pagination.d.ts +31 -0
  163. package/dist/pagination/pagination.d.ts.map +1 -0
  164. package/dist/pagination/pagination.js +213 -0
  165. package/dist/pagination/pagination.js.map +1 -0
  166. package/dist/progress/progress.d.ts.map +1 -1
  167. package/dist/progress/progress.js +5 -7
  168. package/dist/progress/progress.js.map +1 -1
  169. package/dist/query/index.d.ts +3 -0
  170. package/dist/query/index.d.ts.map +1 -0
  171. package/dist/query/index.js +2 -0
  172. package/dist/query/index.js.map +1 -0
  173. package/dist/query/query.d.ts +31 -0
  174. package/dist/query/query.d.ts.map +1 -0
  175. package/dist/query/query.js +150 -0
  176. package/dist/query/query.js.map +1 -0
  177. package/dist/radio-group/index.d.ts +3 -0
  178. package/dist/radio-group/index.d.ts.map +1 -0
  179. package/dist/radio-group/index.js +2 -0
  180. package/dist/radio-group/index.js.map +1 -0
  181. package/dist/radio-group/radio-group.d.ts +37 -0
  182. package/dist/radio-group/radio-group.d.ts.map +1 -0
  183. package/dist/radio-group/radio-group.js +251 -0
  184. package/dist/radio-group/radio-group.js.map +1 -0
  185. package/dist/rating/index.d.ts +3 -0
  186. package/dist/rating/index.d.ts.map +1 -0
  187. package/dist/rating/index.js +2 -0
  188. package/dist/rating/index.js.map +1 -0
  189. package/dist/rating/rating.d.ts +31 -0
  190. package/dist/rating/rating.d.ts.map +1 -0
  191. package/dist/rating/rating.js +187 -0
  192. package/dist/rating/rating.js.map +1 -0
  193. package/dist/router/router.d.ts +16 -1
  194. package/dist/router/router.d.ts.map +1 -1
  195. package/dist/router/router.js +88 -11
  196. package/dist/router/router.js.map +1 -1
  197. package/dist/skeleton/index.d.ts +3 -0
  198. package/dist/skeleton/index.d.ts.map +1 -0
  199. package/dist/skeleton/index.js +2 -0
  200. package/dist/skeleton/index.js.map +1 -0
  201. package/dist/skeleton/skeleton.d.ts +24 -0
  202. package/dist/skeleton/skeleton.d.ts.map +1 -0
  203. package/dist/skeleton/skeleton.js +91 -0
  204. package/dist/skeleton/skeleton.js.map +1 -0
  205. package/dist/slider/index.d.ts +3 -0
  206. package/dist/slider/index.d.ts.map +1 -0
  207. package/dist/slider/index.js +2 -0
  208. package/dist/slider/index.js.map +1 -0
  209. package/dist/slider/slider.d.ts +33 -0
  210. package/dist/slider/slider.d.ts.map +1 -0
  211. package/dist/slider/slider.js +248 -0
  212. package/dist/slider/slider.js.map +1 -0
  213. package/dist/spinner/index.d.ts +3 -0
  214. package/dist/spinner/index.d.ts.map +1 -0
  215. package/dist/spinner/index.js +2 -0
  216. package/dist/spinner/index.js.map +1 -0
  217. package/dist/spinner/spinner.d.ts +23 -0
  218. package/dist/spinner/spinner.d.ts.map +1 -0
  219. package/dist/spinner/spinner.js +82 -0
  220. package/dist/spinner/spinner.js.map +1 -0
  221. package/dist/sw/sw.d.ts.map +1 -1
  222. package/dist/sw/sw.js +39 -7
  223. package/dist/sw/sw.js.map +1 -1
  224. package/dist/switch/index.d.ts +3 -0
  225. package/dist/switch/index.d.ts.map +1 -0
  226. package/dist/switch/index.js +2 -0
  227. package/dist/switch/index.js.map +1 -0
  228. package/dist/switch/switch.d.ts +27 -0
  229. package/dist/switch/switch.d.ts.map +1 -0
  230. package/dist/switch/switch.js +163 -0
  231. package/dist/switch/switch.js.map +1 -0
  232. package/dist/theme/index.d.ts +2 -0
  233. package/dist/theme/index.d.ts.map +1 -1
  234. package/dist/theme/index.js +1 -0
  235. package/dist/theme/index.js.map +1 -1
  236. package/dist/theme/scale.d.ts +40 -0
  237. package/dist/theme/scale.d.ts.map +1 -0
  238. package/dist/theme/scale.js +62 -0
  239. package/dist/theme/scale.js.map +1 -0
  240. package/dist/utils/index.d.ts +29 -0
  241. package/dist/utils/index.d.ts.map +1 -0
  242. package/dist/utils/index.js +114 -0
  243. package/dist/utils/index.js.map +1 -0
  244. package/dist/websocket/index.d.ts +3 -0
  245. package/dist/websocket/index.d.ts.map +1 -0
  246. package/dist/websocket/index.js +2 -0
  247. package/dist/websocket/index.js.map +1 -0
  248. package/dist/websocket/websocket.d.ts +31 -0
  249. package/dist/websocket/websocket.d.ts.map +1 -0
  250. package/dist/websocket/websocket.js +164 -0
  251. package/dist/websocket/websocket.js.map +1 -0
  252. package/package.json +85 -1
@@ -0,0 +1,941 @@
1
+ /**
2
+ * Nova Engine — Notification Center
3
+ *
4
+ * Persistent notification drawer with read/unread state, badge count,
5
+ * date grouping, and an accessible slide-out panel.
6
+ */
7
+ import { trapFocus } from '../a11y/index';
8
+ /* ---- Internal constants ---- */
9
+ let counter = 0;
10
+ function uid() {
11
+ return `nova-nc-${++counter}-${Date.now()}`;
12
+ }
13
+ const TYPE_COLORS = {
14
+ info: 'var(--color-status-info, #3b82f6)',
15
+ success: 'var(--color-status-success, #22c55e)',
16
+ warning: 'var(--color-status-warning, #f59e0b)',
17
+ error: 'var(--color-status-danger, #ef4444)',
18
+ };
19
+ const DEFAULT_ICONS = {
20
+ info: '\u2139\uFE0F',
21
+ success: '\u2705',
22
+ warning: '\u26A0\uFE0F',
23
+ error: '\u274C',
24
+ };
25
+ /* ---- Relative time helper ---- */
26
+ function relativeTime(date) {
27
+ const now = Date.now();
28
+ const diff = now - date.getTime();
29
+ const seconds = Math.floor(diff / 1000);
30
+ const minutes = Math.floor(seconds / 60);
31
+ const hours = Math.floor(minutes / 60);
32
+ const days = Math.floor(hours / 24);
33
+ if (seconds < 60)
34
+ return 'Just now';
35
+ if (minutes < 60)
36
+ return `${minutes} min ago`;
37
+ if (hours < 24)
38
+ return `${hours} hour${hours === 1 ? '' : 's'} ago`;
39
+ if (days === 1)
40
+ return 'Yesterday';
41
+ if (days < 7)
42
+ return `${days} days ago`;
43
+ return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
44
+ }
45
+ /* ---- Date grouping helper ---- */
46
+ function getDateGroup(date) {
47
+ const now = new Date();
48
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
49
+ const yesterday = new Date(today.getTime() - 86400000);
50
+ const target = new Date(date.getFullYear(), date.getMonth(), date.getDate());
51
+ if (target.getTime() === today.getTime())
52
+ return 'Today';
53
+ if (target.getTime() === yesterday.getTime())
54
+ return 'Yesterday';
55
+ return 'Older';
56
+ }
57
+ /* ---- SVG icon builders (no innerHTML) ---- */
58
+ function createBellIcon() {
59
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
60
+ svg.setAttribute('width', '20');
61
+ svg.setAttribute('height', '20');
62
+ svg.setAttribute('viewBox', '0 0 24 24');
63
+ svg.setAttribute('fill', 'none');
64
+ svg.setAttribute('stroke', 'currentColor');
65
+ svg.setAttribute('stroke-width', '2');
66
+ svg.setAttribute('stroke-linecap', 'round');
67
+ svg.setAttribute('stroke-linejoin', 'round');
68
+ svg.setAttribute('aria-hidden', 'true');
69
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
70
+ path.setAttribute('d', 'M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9');
71
+ svg.appendChild(path);
72
+ const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
73
+ path2.setAttribute('d', 'M13.73 21a2 2 0 0 1-3.46 0');
74
+ svg.appendChild(path2);
75
+ return svg;
76
+ }
77
+ function createCloseIcon() {
78
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
79
+ svg.setAttribute('width', '16');
80
+ svg.setAttribute('height', '16');
81
+ svg.setAttribute('viewBox', '0 0 24 24');
82
+ svg.setAttribute('fill', 'none');
83
+ svg.setAttribute('stroke', 'currentColor');
84
+ svg.setAttribute('stroke-width', '2');
85
+ svg.setAttribute('stroke-linecap', 'round');
86
+ svg.setAttribute('stroke-linejoin', 'round');
87
+ svg.setAttribute('aria-hidden', 'true');
88
+ const line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
89
+ line1.setAttribute('x1', '18');
90
+ line1.setAttribute('y1', '6');
91
+ line1.setAttribute('x2', '6');
92
+ line1.setAttribute('y2', '18');
93
+ svg.appendChild(line1);
94
+ const line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
95
+ line2.setAttribute('x1', '6');
96
+ line2.setAttribute('y1', '6');
97
+ line2.setAttribute('x2', '18');
98
+ line2.setAttribute('y2', '18');
99
+ svg.appendChild(line2);
100
+ return svg;
101
+ }
102
+ function createTrashIcon() {
103
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
104
+ svg.setAttribute('width', '14');
105
+ svg.setAttribute('height', '14');
106
+ svg.setAttribute('viewBox', '0 0 24 24');
107
+ svg.setAttribute('fill', 'none');
108
+ svg.setAttribute('stroke', 'currentColor');
109
+ svg.setAttribute('stroke-width', '2');
110
+ svg.setAttribute('stroke-linecap', 'round');
111
+ svg.setAttribute('stroke-linejoin', 'round');
112
+ svg.setAttribute('aria-hidden', 'true');
113
+ const poly = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
114
+ poly.setAttribute('points', '3 6 5 6 21 6');
115
+ svg.appendChild(poly);
116
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
117
+ path.setAttribute('d', 'M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2');
118
+ svg.appendChild(path);
119
+ return svg;
120
+ }
121
+ function createEmptyIcon() {
122
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
123
+ svg.setAttribute('width', '48');
124
+ svg.setAttribute('height', '48');
125
+ svg.setAttribute('viewBox', '0 0 24 24');
126
+ svg.setAttribute('fill', 'none');
127
+ svg.setAttribute('stroke', 'currentColor');
128
+ svg.setAttribute('stroke-width', '1.5');
129
+ svg.setAttribute('stroke-linecap', 'round');
130
+ svg.setAttribute('stroke-linejoin', 'round');
131
+ svg.setAttribute('aria-hidden', 'true');
132
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
133
+ path.setAttribute('d', 'M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9');
134
+ svg.appendChild(path);
135
+ const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
136
+ path2.setAttribute('d', 'M13.73 21a2 2 0 0 1-3.46 0');
137
+ svg.appendChild(path2);
138
+ const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
139
+ line.setAttribute('x1', '1');
140
+ line.setAttribute('y1', '1');
141
+ line.setAttribute('x2', '23');
142
+ line.setAttribute('y2', '23');
143
+ svg.appendChild(line);
144
+ return svg;
145
+ }
146
+ /* ---- Injected styles ---- */
147
+ const STYLE_ID = 'nova-notification-center-styles';
148
+ function injectStyles() {
149
+ if (document.getElementById(STYLE_ID))
150
+ return;
151
+ const s = document.createElement('style');
152
+ s.id = STYLE_ID;
153
+ s.textContent = `
154
+ .nova-nc-trigger {
155
+ position: relative;
156
+ display: inline-flex;
157
+ align-items: center;
158
+ justify-content: center;
159
+ width: 40px;
160
+ height: 40px;
161
+ border: 1px solid var(--color-border, rgba(255, 255, 255, 0.08));
162
+ border-radius: 10px;
163
+ background: var(--color-bg-elevated, rgba(20, 20, 20, 0.95));
164
+ color: var(--color-text-primary, #e5e5e5);
165
+ cursor: pointer;
166
+ padding: 0;
167
+ transition: background 0.15s ease, border-color 0.15s ease;
168
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
169
+ }
170
+ .nova-nc-trigger:hover {
171
+ background: var(--color-bg-hover, rgba(255, 255, 255, 0.06));
172
+ border-color: var(--color-border-hover, rgba(255, 255, 255, 0.14));
173
+ }
174
+ .nova-nc-trigger:focus-visible {
175
+ outline: 2px solid var(--color-accent, #a78bfa);
176
+ outline-offset: 2px;
177
+ }
178
+ .nova-nc-badge {
179
+ position: absolute;
180
+ top: -4px;
181
+ right: -4px;
182
+ min-width: 18px;
183
+ height: 18px;
184
+ padding: 0 5px;
185
+ border-radius: 9px;
186
+ background: var(--color-status-danger, #ef4444);
187
+ color: #fff;
188
+ font-size: 11px;
189
+ font-weight: 600;
190
+ line-height: 18px;
191
+ text-align: center;
192
+ pointer-events: none;
193
+ opacity: 0;
194
+ transform: scale(0.5);
195
+ transition: opacity 0.2s ease, transform 0.2s ease;
196
+ }
197
+ .nova-nc-badge--visible {
198
+ opacity: 1;
199
+ transform: scale(1);
200
+ }
201
+ .nova-nc-badge--pulse {
202
+ animation: nova-nc-pulse 0.6s ease-out;
203
+ }
204
+ @keyframes nova-nc-pulse {
205
+ 0% { transform: scale(1); }
206
+ 40% { transform: scale(1.3); }
207
+ 100% { transform: scale(1); }
208
+ }
209
+ .nova-nc-backdrop {
210
+ position: fixed;
211
+ inset: 0;
212
+ z-index: 99998;
213
+ background: var(--color-bg-overlay, rgba(0, 0, 0, 0.4));
214
+ opacity: 0;
215
+ transition: opacity 0.2s ease;
216
+ }
217
+ .nova-nc-backdrop--visible {
218
+ opacity: 1;
219
+ }
220
+ .nova-nc-panel {
221
+ position: fixed;
222
+ top: 0;
223
+ bottom: 0;
224
+ z-index: 99999;
225
+ width: min(400px, 90vw);
226
+ display: flex;
227
+ flex-direction: column;
228
+ background: var(--color-bg-elevated, rgba(22, 22, 22, 0.98));
229
+ border: 1px solid var(--color-border, rgba(255, 255, 255, 0.08));
230
+ box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);
231
+ backdrop-filter: blur(16px);
232
+ color: var(--color-text-primary, #e5e5e5);
233
+ font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
234
+ transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
235
+ }
236
+ .nova-nc-panel--right {
237
+ right: 0;
238
+ border-radius: 14px 0 0 14px;
239
+ transform: translateX(100%);
240
+ }
241
+ .nova-nc-panel--left {
242
+ left: 0;
243
+ border-radius: 0 14px 14px 0;
244
+ transform: translateX(-100%);
245
+ }
246
+ .nova-nc-panel--open {
247
+ transform: translateX(0);
248
+ }
249
+ .nova-nc-header {
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: space-between;
253
+ padding: 16px 20px;
254
+ border-bottom: 1px solid var(--color-border, rgba(255, 255, 255, 0.08));
255
+ flex-shrink: 0;
256
+ }
257
+ .nova-nc-header__title {
258
+ font-size: 16px;
259
+ font-weight: 600;
260
+ margin: 0;
261
+ }
262
+ .nova-nc-header__actions {
263
+ display: flex;
264
+ align-items: center;
265
+ gap: 8px;
266
+ }
267
+ .nova-nc-btn {
268
+ background: none;
269
+ border: 1px solid transparent;
270
+ color: var(--color-text-secondary, rgba(255, 255, 255, 0.5));
271
+ cursor: pointer;
272
+ padding: 4px 10px;
273
+ border-radius: 6px;
274
+ font: inherit;
275
+ font-size: 12px;
276
+ transition: color 0.15s ease, background 0.15s ease;
277
+ display: inline-flex;
278
+ align-items: center;
279
+ gap: 4px;
280
+ }
281
+ .nova-nc-btn:hover {
282
+ color: var(--color-text-primary, #e5e5e5);
283
+ background: rgba(255, 255, 255, 0.06);
284
+ }
285
+ .nova-nc-btn:focus-visible {
286
+ outline: 2px solid var(--color-accent, #a78bfa);
287
+ outline-offset: 1px;
288
+ }
289
+ .nova-nc-close {
290
+ background: none;
291
+ border: none;
292
+ color: var(--color-text-secondary, rgba(255, 255, 255, 0.4));
293
+ cursor: pointer;
294
+ padding: 4px;
295
+ border-radius: 6px;
296
+ display: inline-flex;
297
+ align-items: center;
298
+ justify-content: center;
299
+ transition: color 0.15s ease, background 0.15s ease;
300
+ }
301
+ .nova-nc-close:hover {
302
+ color: var(--color-text-primary, #e5e5e5);
303
+ background: rgba(255, 255, 255, 0.06);
304
+ }
305
+ .nova-nc-close:focus-visible {
306
+ outline: 2px solid var(--color-accent, #a78bfa);
307
+ outline-offset: 1px;
308
+ }
309
+ .nova-nc-list {
310
+ flex: 1;
311
+ overflow-y: auto;
312
+ overscroll-behavior: contain;
313
+ }
314
+ .nova-nc-group-header {
315
+ padding: 8px 20px 4px;
316
+ font-size: 11px;
317
+ font-weight: 600;
318
+ text-transform: uppercase;
319
+ letter-spacing: 0.05em;
320
+ color: var(--color-text-secondary, rgba(255, 255, 255, 0.4));
321
+ }
322
+ .nova-nc-item {
323
+ position: relative;
324
+ display: flex;
325
+ align-items: flex-start;
326
+ gap: 12px;
327
+ padding: 12px 20px;
328
+ cursor: pointer;
329
+ transition: background 0.15s ease;
330
+ overflow: hidden;
331
+ }
332
+ .nova-nc-item:hover {
333
+ background: var(--color-bg-hover, rgba(255, 255, 255, 0.04));
334
+ }
335
+ .nova-nc-item:focus-visible {
336
+ outline: 2px solid var(--color-accent, #a78bfa);
337
+ outline-offset: -2px;
338
+ }
339
+ .nova-nc-item--unread {
340
+ background: rgba(255, 255, 255, 0.02);
341
+ }
342
+ .nova-nc-item__border {
343
+ position: absolute;
344
+ left: 0;
345
+ top: 0;
346
+ bottom: 0;
347
+ width: 3px;
348
+ }
349
+ .nova-nc-item__icon {
350
+ flex-shrink: 0;
351
+ width: 32px;
352
+ height: 32px;
353
+ border-radius: 8px;
354
+ display: flex;
355
+ align-items: center;
356
+ justify-content: center;
357
+ font-size: 16px;
358
+ margin-top: 2px;
359
+ }
360
+ .nova-nc-item__body {
361
+ flex: 1;
362
+ min-width: 0;
363
+ }
364
+ .nova-nc-item__title {
365
+ font-size: 13px;
366
+ line-height: 1.4;
367
+ margin-bottom: 2px;
368
+ display: flex;
369
+ align-items: center;
370
+ gap: 6px;
371
+ }
372
+ .nova-nc-item__title-text {
373
+ overflow: hidden;
374
+ text-overflow: ellipsis;
375
+ white-space: nowrap;
376
+ }
377
+ .nova-nc-item--unread .nova-nc-item__title-text {
378
+ font-weight: 600;
379
+ }
380
+ .nova-nc-item__message {
381
+ font-size: 12px;
382
+ line-height: 1.4;
383
+ color: var(--color-text-secondary, rgba(255, 255, 255, 0.6));
384
+ display: -webkit-box;
385
+ -webkit-line-clamp: 2;
386
+ -webkit-box-orient: vertical;
387
+ overflow: hidden;
388
+ }
389
+ .nova-nc-item__time {
390
+ font-size: 11px;
391
+ color: var(--color-text-secondary, rgba(255, 255, 255, 0.35));
392
+ margin-top: 4px;
393
+ }
394
+ .nova-nc-item__unread-dot {
395
+ width: 8px;
396
+ height: 8px;
397
+ border-radius: 50%;
398
+ background: var(--color-accent, #a78bfa);
399
+ flex-shrink: 0;
400
+ margin-top: 4px;
401
+ }
402
+ .nova-nc-item__action {
403
+ margin-top: 6px;
404
+ padding: 3px 10px;
405
+ border-radius: 5px;
406
+ border: 1px solid var(--color-border, rgba(255, 255, 255, 0.12));
407
+ background: transparent;
408
+ color: var(--color-text-primary, #e5e5e5);
409
+ font-size: 12px;
410
+ cursor: pointer;
411
+ transition: background 0.15s ease;
412
+ }
413
+ .nova-nc-item__action:hover {
414
+ background: rgba(255, 255, 255, 0.06);
415
+ }
416
+ .nova-nc-item__action:focus-visible {
417
+ outline: 2px solid var(--color-accent, #a78bfa);
418
+ outline-offset: 1px;
419
+ }
420
+ .nova-nc-item__delete {
421
+ position: absolute;
422
+ right: 0;
423
+ top: 0;
424
+ bottom: 0;
425
+ width: 44px;
426
+ display: flex;
427
+ align-items: center;
428
+ justify-content: center;
429
+ background: var(--color-status-danger, #ef4444);
430
+ color: #fff;
431
+ border: none;
432
+ cursor: pointer;
433
+ opacity: 0;
434
+ transform: translateX(100%);
435
+ transition: opacity 0.15s ease, transform 0.15s ease;
436
+ }
437
+ .nova-nc-item:hover .nova-nc-item__delete,
438
+ .nova-nc-item__delete:focus-visible {
439
+ opacity: 1;
440
+ transform: translateX(0);
441
+ }
442
+ .nova-nc-item__delete:focus-visible {
443
+ outline: 2px solid var(--color-accent, #a78bfa);
444
+ outline-offset: -2px;
445
+ }
446
+ .nova-nc-empty {
447
+ display: flex;
448
+ flex-direction: column;
449
+ align-items: center;
450
+ justify-content: center;
451
+ padding: 60px 20px;
452
+ color: var(--color-text-secondary, rgba(255, 255, 255, 0.3));
453
+ text-align: center;
454
+ gap: 12px;
455
+ }
456
+ .nova-nc-empty__text {
457
+ font-size: 14px;
458
+ }
459
+ @media (prefers-reduced-motion: reduce) {
460
+ .nova-nc-panel,
461
+ .nova-nc-backdrop,
462
+ .nova-nc-badge,
463
+ .nova-nc-item__delete,
464
+ .nova-nc-item { transition: none; }
465
+ .nova-nc-badge--pulse { animation: none; }
466
+ }`;
467
+ document.head.appendChild(s);
468
+ }
469
+ /* ---- Resolve trigger ---- */
470
+ function resolveTrigger(trigger) {
471
+ if (typeof trigger === 'string') {
472
+ const el = document.querySelector(trigger);
473
+ if (!el)
474
+ throw new Error(`[Nova NotificationCenter] Trigger not found: ${trigger}`);
475
+ return el;
476
+ }
477
+ return trigger;
478
+ }
479
+ /* ---- Factory ---- */
480
+ /** Create a notification center with persistent notifications, badge, and slide-out panel. */
481
+ export function createNotificationCenter(options = {}) {
482
+ const { trigger: triggerOpt, position = 'right', maxItems = 100, groupByDate = true, onNotificationClick, onUnreadChange, } = options;
483
+ injectStyles();
484
+ let destroyed = false;
485
+ let panelOpen = false;
486
+ let previousFocus = null;
487
+ let releaseFocusTrap = null;
488
+ let updateTimer = null;
489
+ const notifications = [];
490
+ const listeners = [];
491
+ function on(target, evt, fn) {
492
+ target.addEventListener(evt, fn);
493
+ listeners.push([target, evt, fn]);
494
+ }
495
+ /* ---- Build trigger button ---- */
496
+ let triggerBtn = null;
497
+ let badgeEl = null;
498
+ let triggerHost = null;
499
+ if (triggerOpt !== undefined) {
500
+ triggerHost = resolveTrigger(triggerOpt);
501
+ triggerBtn = document.createElement('button');
502
+ triggerBtn.className = 'nova-nc-trigger';
503
+ triggerBtn.setAttribute('type', 'button');
504
+ triggerBtn.setAttribute('aria-label', 'Notifications');
505
+ triggerBtn.setAttribute('aria-expanded', 'false');
506
+ triggerBtn.setAttribute('aria-haspopup', 'true');
507
+ triggerBtn.appendChild(createBellIcon());
508
+ badgeEl = document.createElement('span');
509
+ badgeEl.className = 'nova-nc-badge';
510
+ badgeEl.setAttribute('aria-hidden', 'true');
511
+ triggerBtn.appendChild(badgeEl);
512
+ triggerHost.appendChild(triggerBtn);
513
+ on(triggerBtn, 'click', () => {
514
+ if (destroyed)
515
+ return;
516
+ instance.toggle();
517
+ });
518
+ }
519
+ /* ---- Build backdrop ---- */
520
+ const backdrop = document.createElement('div');
521
+ backdrop.className = 'nova-nc-backdrop';
522
+ on(backdrop, 'click', () => {
523
+ if (destroyed)
524
+ return;
525
+ instance.close();
526
+ });
527
+ /* ---- Build panel ---- */
528
+ const panel = document.createElement('div');
529
+ panel.className = `nova-nc-panel nova-nc-panel--${position}`;
530
+ panel.setAttribute('role', 'dialog');
531
+ panel.setAttribute('aria-label', 'Notification center');
532
+ panel.setAttribute('aria-modal', 'true');
533
+ // Header
534
+ const header = document.createElement('div');
535
+ header.className = 'nova-nc-header';
536
+ const headerTitle = document.createElement('h2');
537
+ headerTitle.className = 'nova-nc-header__title';
538
+ headerTitle.textContent = 'Notifications';
539
+ header.appendChild(headerTitle);
540
+ const headerActions = document.createElement('div');
541
+ headerActions.className = 'nova-nc-header__actions';
542
+ const markAllBtn = document.createElement('button');
543
+ markAllBtn.className = 'nova-nc-btn';
544
+ markAllBtn.setAttribute('type', 'button');
545
+ markAllBtn.textContent = 'Mark all read';
546
+ on(markAllBtn, 'click', () => {
547
+ if (destroyed)
548
+ return;
549
+ instance.markAllRead();
550
+ });
551
+ headerActions.appendChild(markAllBtn);
552
+ const closeBtn = document.createElement('button');
553
+ closeBtn.className = 'nova-nc-close';
554
+ closeBtn.setAttribute('type', 'button');
555
+ closeBtn.setAttribute('aria-label', 'Close notifications');
556
+ closeBtn.appendChild(createCloseIcon());
557
+ on(closeBtn, 'click', () => {
558
+ if (destroyed)
559
+ return;
560
+ instance.close();
561
+ });
562
+ headerActions.appendChild(closeBtn);
563
+ header.appendChild(headerActions);
564
+ panel.appendChild(header);
565
+ // List container
566
+ const listEl = document.createElement('div');
567
+ listEl.className = 'nova-nc-list';
568
+ panel.appendChild(listEl);
569
+ /* ---- Keyboard handler ---- */
570
+ function onKeyDown(e) {
571
+ if (!panelOpen || destroyed)
572
+ return;
573
+ if (e.key === 'Escape') {
574
+ e.preventDefault();
575
+ instance.close();
576
+ }
577
+ }
578
+ on(document, 'keydown', onKeyDown);
579
+ /* ---- Badge update ---- */
580
+ function updateBadge(pulse = false) {
581
+ if (!badgeEl)
582
+ return;
583
+ const count = getUnreadCount();
584
+ if (count === 0) {
585
+ badgeEl.textContent = '';
586
+ badgeEl.classList.remove('nova-nc-badge--visible');
587
+ }
588
+ else {
589
+ badgeEl.textContent = count > 99 ? '99+' : String(count);
590
+ badgeEl.classList.add('nova-nc-badge--visible');
591
+ if (pulse) {
592
+ badgeEl.classList.remove('nova-nc-badge--pulse');
593
+ // Force reflow to restart the animation
594
+ void badgeEl.offsetWidth;
595
+ badgeEl.classList.add('nova-nc-badge--pulse');
596
+ }
597
+ }
598
+ onUnreadChange?.(count);
599
+ }
600
+ /* ---- Render notifications into panel ---- */
601
+ function renderList() {
602
+ // Clear existing content
603
+ while (listEl.firstChild) {
604
+ listEl.removeChild(listEl.firstChild);
605
+ }
606
+ if (notifications.length === 0) {
607
+ const empty = document.createElement('div');
608
+ empty.className = 'nova-nc-empty';
609
+ empty.appendChild(createEmptyIcon());
610
+ const text = document.createElement('div');
611
+ text.className = 'nova-nc-empty__text';
612
+ text.textContent = 'No notifications';
613
+ empty.appendChild(text);
614
+ listEl.appendChild(empty);
615
+ return;
616
+ }
617
+ if (groupByDate) {
618
+ const groups = new Map();
619
+ for (const n of notifications) {
620
+ const group = getDateGroup(n.timestamp);
621
+ const arr = groups.get(group);
622
+ if (arr) {
623
+ arr.push(n);
624
+ }
625
+ else {
626
+ groups.set(group, [n]);
627
+ }
628
+ }
629
+ // Maintain stable order: Today, Yesterday, Older
630
+ const order = ['Today', 'Yesterday', 'Older'];
631
+ for (const label of order) {
632
+ const items = groups.get(label);
633
+ if (!items || items.length === 0)
634
+ continue;
635
+ const groupHeader = document.createElement('div');
636
+ groupHeader.className = 'nova-nc-group-header';
637
+ groupHeader.textContent = label;
638
+ listEl.appendChild(groupHeader);
639
+ for (const n of items) {
640
+ listEl.appendChild(renderItem(n));
641
+ }
642
+ }
643
+ }
644
+ else {
645
+ for (const n of notifications) {
646
+ listEl.appendChild(renderItem(n));
647
+ }
648
+ }
649
+ }
650
+ function renderItem(n) {
651
+ const item = document.createElement('div');
652
+ item.className = 'nova-nc-item' + (n.read ? '' : ' nova-nc-item--unread');
653
+ item.setAttribute('role', 'article');
654
+ item.setAttribute('aria-label', n.title);
655
+ item.setAttribute('tabindex', '0');
656
+ item.dataset.id = n.id;
657
+ // Coloured left border
658
+ const border = document.createElement('div');
659
+ border.className = 'nova-nc-item__border';
660
+ border.style.background = TYPE_COLORS[n.type];
661
+ item.appendChild(border);
662
+ // Icon
663
+ const icon = document.createElement('div');
664
+ icon.className = 'nova-nc-item__icon';
665
+ icon.style.background = TYPE_COLORS[n.type].replace(')', ', 0.12)').replace('var(', 'color-mix(in srgb, ');
666
+ // Fallback: just use a subtle bg
667
+ icon.style.background = `rgba(128, 128, 128, 0.1)`;
668
+ icon.textContent = n.icon || DEFAULT_ICONS[n.type];
669
+ item.appendChild(icon);
670
+ // Body
671
+ const body = document.createElement('div');
672
+ body.className = 'nova-nc-item__body';
673
+ const title = document.createElement('div');
674
+ title.className = 'nova-nc-item__title';
675
+ const titleText = document.createElement('span');
676
+ titleText.className = 'nova-nc-item__title-text';
677
+ titleText.textContent = n.title;
678
+ title.appendChild(titleText);
679
+ body.appendChild(title);
680
+ const message = document.createElement('div');
681
+ message.className = 'nova-nc-item__message';
682
+ message.textContent = n.message;
683
+ body.appendChild(message);
684
+ const time = document.createElement('div');
685
+ time.className = 'nova-nc-item__time';
686
+ time.textContent = relativeTime(n.timestamp);
687
+ body.appendChild(time);
688
+ if (n.action) {
689
+ const actionBtn = document.createElement('button');
690
+ actionBtn.className = 'nova-nc-item__action';
691
+ actionBtn.setAttribute('type', 'button');
692
+ actionBtn.textContent = n.action.label;
693
+ const actionFn = n.action.onClick;
694
+ on(actionBtn, 'click', (e) => {
695
+ e.stopPropagation();
696
+ actionFn();
697
+ });
698
+ body.appendChild(actionBtn);
699
+ }
700
+ item.appendChild(body);
701
+ // Unread dot
702
+ if (!n.read) {
703
+ const dot = document.createElement('div');
704
+ dot.className = 'nova-nc-item__unread-dot';
705
+ item.appendChild(dot);
706
+ }
707
+ // Delete button (revealed on hover)
708
+ const deleteBtn = document.createElement('button');
709
+ deleteBtn.className = 'nova-nc-item__delete';
710
+ deleteBtn.setAttribute('type', 'button');
711
+ deleteBtn.setAttribute('aria-label', `Delete notification: ${n.title}`);
712
+ deleteBtn.appendChild(createTrashIcon());
713
+ on(deleteBtn, 'click', (e) => {
714
+ e.stopPropagation();
715
+ instance.remove(n.id);
716
+ });
717
+ item.appendChild(deleteBtn);
718
+ // Click handler — mark as read + callback
719
+ on(item, 'click', () => {
720
+ if (destroyed)
721
+ return;
722
+ if (!n.read) {
723
+ instance.markRead(n.id);
724
+ }
725
+ onNotificationClick?.(n);
726
+ });
727
+ // Enter/Space activates
728
+ on(item, 'keydown', ((e) => {
729
+ if (e.key === 'Enter' || e.key === ' ') {
730
+ e.preventDefault();
731
+ item.click();
732
+ }
733
+ }));
734
+ return item;
735
+ }
736
+ /* ---- Unread count ---- */
737
+ function getUnreadCount() {
738
+ let count = 0;
739
+ for (const n of notifications) {
740
+ if (!n.read)
741
+ count++;
742
+ }
743
+ return count;
744
+ }
745
+ /* ---- Timestamp refresh timer ---- */
746
+ function startTimestampRefresh() {
747
+ if (updateTimer !== null)
748
+ return;
749
+ updateTimer = setInterval(() => {
750
+ if (panelOpen && !destroyed) {
751
+ const timeEls = listEl.querySelectorAll('.nova-nc-item__time');
752
+ timeEls.forEach((el, i) => {
753
+ if (i < notifications.length) {
754
+ el.textContent = relativeTime(notifications[i].timestamp);
755
+ }
756
+ });
757
+ }
758
+ }, 30000); // Refresh every 30 seconds
759
+ }
760
+ function stopTimestampRefresh() {
761
+ if (updateTimer !== null) {
762
+ clearInterval(updateTimer);
763
+ updateTimer = null;
764
+ }
765
+ }
766
+ /* ---- Panel open / close ---- */
767
+ function openPanel() {
768
+ if (panelOpen || destroyed)
769
+ return;
770
+ panelOpen = true;
771
+ previousFocus = document.activeElement;
772
+ document.body.appendChild(backdrop);
773
+ document.body.appendChild(panel);
774
+ document.body.style.overflow = 'hidden';
775
+ renderList();
776
+ requestAnimationFrame(() => {
777
+ backdrop.classList.add('nova-nc-backdrop--visible');
778
+ panel.classList.add('nova-nc-panel--open');
779
+ });
780
+ if (triggerBtn) {
781
+ triggerBtn.setAttribute('aria-expanded', 'true');
782
+ }
783
+ // Activate focus trap after transition
784
+ releaseFocusTrap = trapFocus(panel);
785
+ startTimestampRefresh();
786
+ }
787
+ function closePanel() {
788
+ if (!panelOpen || destroyed)
789
+ return;
790
+ panelOpen = false;
791
+ backdrop.classList.remove('nova-nc-backdrop--visible');
792
+ panel.classList.remove('nova-nc-panel--open');
793
+ if (triggerBtn) {
794
+ triggerBtn.setAttribute('aria-expanded', 'false');
795
+ }
796
+ if (releaseFocusTrap) {
797
+ releaseFocusTrap();
798
+ releaseFocusTrap = null;
799
+ }
800
+ stopTimestampRefresh();
801
+ const finish = () => {
802
+ if (backdrop.parentNode)
803
+ backdrop.remove();
804
+ if (panel.parentNode)
805
+ panel.remove();
806
+ document.body.style.overflow = '';
807
+ previousFocus?.focus();
808
+ };
809
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
810
+ finish();
811
+ }
812
+ else {
813
+ panel.addEventListener('transitionend', finish, { once: true });
814
+ setTimeout(finish, 300); // fallback
815
+ }
816
+ }
817
+ /* ---- Instance ---- */
818
+ const instance = {
819
+ add(partial) {
820
+ if (destroyed)
821
+ throw new Error('[Nova NotificationCenter] Instance has been destroyed');
822
+ const n = {
823
+ id: uid(),
824
+ type: partial.type,
825
+ title: partial.title,
826
+ message: partial.message,
827
+ timestamp: new Date(),
828
+ read: false,
829
+ action: partial.action,
830
+ icon: partial.icon,
831
+ };
832
+ // Add to the front (newest first)
833
+ notifications.unshift(n);
834
+ // Enforce max
835
+ while (notifications.length > maxItems) {
836
+ notifications.pop();
837
+ }
838
+ updateBadge(!panelOpen);
839
+ if (panelOpen) {
840
+ renderList();
841
+ }
842
+ return n;
843
+ },
844
+ info(title, message) {
845
+ return instance.add({ type: 'info', title, message });
846
+ },
847
+ success(title, message) {
848
+ return instance.add({ type: 'success', title, message });
849
+ },
850
+ warning(title, message) {
851
+ return instance.add({ type: 'warning', title, message });
852
+ },
853
+ error(title, message) {
854
+ return instance.add({ type: 'error', title, message });
855
+ },
856
+ markRead(id) {
857
+ if (destroyed)
858
+ return;
859
+ for (const n of notifications) {
860
+ if (n.id === id) {
861
+ n.read = true;
862
+ break;
863
+ }
864
+ }
865
+ updateBadge();
866
+ if (panelOpen)
867
+ renderList();
868
+ },
869
+ markAllRead() {
870
+ if (destroyed)
871
+ return;
872
+ for (const n of notifications) {
873
+ n.read = true;
874
+ }
875
+ updateBadge();
876
+ if (panelOpen)
877
+ renderList();
878
+ },
879
+ remove(id) {
880
+ if (destroyed)
881
+ return;
882
+ const idx = notifications.findIndex((n) => n.id === id);
883
+ if (idx !== -1) {
884
+ notifications.splice(idx, 1);
885
+ updateBadge();
886
+ if (panelOpen)
887
+ renderList();
888
+ }
889
+ },
890
+ clear() {
891
+ if (destroyed)
892
+ return;
893
+ notifications.length = 0;
894
+ updateBadge();
895
+ if (panelOpen)
896
+ renderList();
897
+ },
898
+ getUnreadCount,
899
+ getAll() {
900
+ return notifications.slice();
901
+ },
902
+ open: openPanel,
903
+ close: closePanel,
904
+ toggle() {
905
+ if (panelOpen)
906
+ closePanel();
907
+ else
908
+ openPanel();
909
+ },
910
+ isOpen() {
911
+ return panelOpen;
912
+ },
913
+ destroy() {
914
+ if (destroyed)
915
+ return;
916
+ destroyed = true;
917
+ stopTimestampRefresh();
918
+ if (panelOpen) {
919
+ if (releaseFocusTrap) {
920
+ releaseFocusTrap();
921
+ releaseFocusTrap = null;
922
+ }
923
+ if (backdrop.parentNode)
924
+ backdrop.remove();
925
+ if (panel.parentNode)
926
+ panel.remove();
927
+ document.body.style.overflow = '';
928
+ }
929
+ if (triggerBtn && triggerBtn.parentNode) {
930
+ triggerBtn.remove();
931
+ }
932
+ for (const [t, e, fn] of listeners) {
933
+ t.removeEventListener(e, fn);
934
+ }
935
+ listeners.length = 0;
936
+ notifications.length = 0;
937
+ },
938
+ };
939
+ return instance;
940
+ }
941
+ //# sourceMappingURL=notification-center.js.map