nostr-components 0.3.0 → 0.3.2

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 (131) hide show
  1. package/README.md +58 -46
  2. package/dist/assets/{base-styles-DC0ilu4S.js → base-styles-Dmuzg8I4.js} +3 -3
  3. package/dist/assets/{base-styles-DC0ilu4S.js.map → base-styles-Dmuzg8I4.js.map} +1 -1
  4. package/dist/assets/{copy-delegation-CcagQMIW.js → copy-delegation-xzt-t_do.js} +5 -5
  5. package/dist/assets/{copy-delegation-CcagQMIW.js.map → copy-delegation-xzt-t_do.js.map} +1 -1
  6. package/dist/assets/dark-nostrich-running.gif +0 -0
  7. package/dist/assets/default_dp-NQ3TGrtT.png +0 -0
  8. package/dist/assets/default_dp.png +0 -0
  9. package/dist/assets/default_dp_32.png +0 -0
  10. package/dist/assets/{dialog-component-Dqg0QU9I.js → dialog-component-Da1ZIYh9.js} +7 -2
  11. package/dist/assets/dialog-component-Da1ZIYh9.js.map +1 -0
  12. package/dist/assets/{dialog-likers-D3c7WIMp.js → dialog-likers-Bq6kUbS0.js} +4 -4
  13. package/dist/assets/{dialog-likers-D3c7WIMp.js.map → dialog-likers-Bq6kUbS0.js.map} +1 -1
  14. package/dist/assets/light-nostrich-running.gif +0 -0
  15. package/dist/assets/{nostr-service-m3Hgc5Xx.js → nostr-service-CA0Qx4nJ.js} +3 -3
  16. package/dist/assets/{nostr-service-m3Hgc5Xx.js.map → nostr-service-CA0Qx4nJ.js.map} +1 -1
  17. package/dist/assets/nostr-user-component-r-MUbTL6.js +2 -0
  18. package/dist/assets/nostr-user-component-r-MUbTL6.js.map +1 -0
  19. package/dist/assets/{pure-laCRX9eG.js → pure-DOoUcNQv.js} +2 -2
  20. package/dist/assets/{pure-laCRX9eG.js.map → pure-DOoUcNQv.js.map} +1 -1
  21. package/dist/assets/{theme-C1r1Zw8r.js → theme-BN1Bvweb.js} +2 -2
  22. package/dist/assets/{theme-C1r1Zw8r.js.map → theme-BN1Bvweb.js.map} +1 -1
  23. package/dist/assets/{user-resolver-DqI5KGh6.js → user-resolver-ArI0680e.js} +2 -2
  24. package/dist/assets/{user-resolver-DqI5KGh6.js.map → user-resolver-ArI0680e.js.map} +1 -1
  25. package/dist/assets/zap-utils-BiKkJPt6.js +2 -0
  26. package/dist/assets/zap-utils-BiKkJPt6.js.map +1 -0
  27. package/dist/components/nostr-comment.es.js +6 -6
  28. package/dist/components/nostr-comment.es.js.map +1 -1
  29. package/dist/components/nostr-dm.es.js +2 -2
  30. package/dist/components/nostr-dm.es.js.map +1 -1
  31. package/dist/components/nostr-follow-button.es.js +3 -3
  32. package/dist/components/nostr-follow-button.es.js.map +1 -1
  33. package/dist/components/nostr-like.es.js +6 -6
  34. package/dist/components/nostr-like.es.js.map +1 -1
  35. package/dist/components/nostr-live-chat.es.js +3 -3
  36. package/dist/components/nostr-live-chat.es.js.map +1 -1
  37. package/dist/components/nostr-post.es.js +2 -2
  38. package/dist/components/nostr-post.es.js.map +1 -1
  39. package/dist/components/nostr-profile-badge.es.js +3 -3
  40. package/dist/components/nostr-profile-badge.es.js.map +1 -1
  41. package/dist/components/nostr-profile.es.js +5 -5
  42. package/dist/components/nostr-profile.es.js.map +1 -1
  43. package/dist/components/nostr-zap.es.js +5 -5
  44. package/dist/components/nostr-zap.es.js.map +1 -1
  45. package/dist/index.d.ts +6 -0
  46. package/dist/nostr-comment.d.ts +4 -0
  47. package/dist/nostr-components.es.js +1 -1
  48. package/dist/nostr-components.es.js.map +1 -1
  49. package/dist/nostr-components.umd.js +69 -40
  50. package/dist/nostr-components.umd.js.map +1 -1
  51. package/dist/nostr-dm.d.ts +4 -0
  52. package/dist/nostr-follow-button.d.ts +4 -0
  53. package/dist/nostr-like.d.ts +4 -0
  54. package/dist/nostr-live-chat.d.ts +4 -0
  55. package/dist/nostr-post.d.ts +4 -0
  56. package/dist/nostr-profile-badge.d.ts +4 -0
  57. package/dist/nostr-profile.d.ts +4 -0
  58. package/dist/nostr-zap.d.ts +4 -0
  59. package/dist/src/base/base-component/nostr-base-component.d.ts +116 -0
  60. package/dist/src/base/copy-delegation.d.ts +5 -0
  61. package/dist/src/base/dialog-component/dialog-component.d.ts +67 -0
  62. package/dist/src/base/dialog-component/style.d.ts +5 -0
  63. package/dist/src/base/event-component/nostr-event-component.d.ts +53 -0
  64. package/dist/src/base/render-options.d.ts +5 -0
  65. package/dist/src/base/resolvers/event-resolver.d.ts +20 -0
  66. package/dist/src/base/resolvers/user-resolver.d.ts +19 -0
  67. package/dist/src/base/text-row/render-name.d.ts +7 -0
  68. package/dist/src/base/text-row/render-nip05.d.ts +1 -0
  69. package/dist/src/base/text-row/render-npub.d.ts +1 -0
  70. package/dist/src/base/text-row/render-text-row.d.ts +9 -0
  71. package/dist/src/base/user-component/nostr-user-component.d.ts +43 -0
  72. package/dist/src/common/base-styles.d.ts +44 -0
  73. package/dist/src/common/constants.d.ts +4 -0
  74. package/dist/src/common/date-utils.d.ts +9 -0
  75. package/dist/src/common/icons.d.ts +7 -0
  76. package/dist/src/common/nip05-utils.d.ts +13 -0
  77. package/dist/src/common/nostr-login-service.d.ts +26 -0
  78. package/dist/src/common/nostr-service.d.ts +40 -0
  79. package/dist/src/common/theme.d.ts +4 -0
  80. package/dist/src/common/types.d.ts +1 -0
  81. package/dist/src/common/utils.d.ts +34 -0
  82. package/dist/src/index.d.ts +32 -0
  83. package/dist/src/nostr-comment/nostr-comment.d.ts +60 -0
  84. package/dist/src/nostr-comment/render.d.ts +15 -0
  85. package/dist/src/nostr-comment/utils.d.ts +81 -0
  86. package/dist/src/nostr-dm/nostr-dm.d.ts +34 -0
  87. package/dist/src/nostr-dm/render.d.ts +15 -0
  88. package/dist/src/nostr-follow-button/nostr-follow-button.d.ts +24 -0
  89. package/dist/src/nostr-follow-button/render.d.ts +11 -0
  90. package/dist/src/nostr-follow-button/style.d.ts +1 -0
  91. package/dist/src/nostr-like/dialog-help-style.d.ts +1 -0
  92. package/dist/src/nostr-like/dialog-help.d.ts +2 -0
  93. package/dist/src/nostr-like/dialog-likers-style.d.ts +1 -0
  94. package/dist/src/nostr-like/dialog-likers.d.ts +24 -0
  95. package/dist/src/nostr-like/like-utils.d.ts +48 -0
  96. package/dist/src/nostr-like/nostr-like.d.ts +49 -0
  97. package/dist/src/nostr-like/render.d.ts +10 -0
  98. package/dist/src/nostr-like/style.d.ts +1 -0
  99. package/dist/src/nostr-live-chat/nostr-live-chat.d.ts +65 -0
  100. package/dist/src/nostr-live-chat/render.d.ts +31 -0
  101. package/dist/src/nostr-post/nostr-post.d.ts +25 -0
  102. package/dist/src/nostr-post/parse-text.d.ts +8 -0
  103. package/dist/src/nostr-post/render-content.d.ts +5 -0
  104. package/dist/src/nostr-post/render.d.ts +19 -0
  105. package/dist/src/nostr-post/style.d.ts +1 -0
  106. package/dist/src/nostr-profile/nostr-profile.d.ts +24 -0
  107. package/dist/src/nostr-profile/render-stats.d.ts +1 -0
  108. package/dist/src/nostr-profile/render.d.ts +22 -0
  109. package/dist/src/nostr-profile/style.d.ts +1 -0
  110. package/dist/src/nostr-profile-badge/nostr-profile-badge.d.ts +34 -0
  111. package/dist/src/nostr-profile-badge/render.d.ts +11 -0
  112. package/dist/src/nostr-profile-badge/style.d.ts +1 -0
  113. package/dist/src/nostr-zap/dialog-help-style.d.ts +5 -0
  114. package/dist/src/nostr-zap/dialog-help.d.ts +2 -0
  115. package/dist/src/nostr-zap/dialog-zap-style.d.ts +6 -0
  116. package/dist/src/nostr-zap/dialog-zap.d.ts +31 -0
  117. package/dist/src/nostr-zap/dialog-zappers-style.d.ts +1 -0
  118. package/dist/src/nostr-zap/dialog-zappers.d.ts +25 -0
  119. package/dist/src/nostr-zap/nostr-zap.d.ts +45 -0
  120. package/dist/src/nostr-zap/render.d.ts +9 -0
  121. package/dist/src/nostr-zap/style.d.ts +1 -0
  122. package/dist/src/nostr-zap/zap-utils.d.ts +57 -0
  123. package/dist/vite.config.d.ts +2 -0
  124. package/dist/vite.config.esm.d.ts +2 -0
  125. package/dist/vite.config.umd.d.ts +2 -0
  126. package/package.json +1 -1
  127. package/dist/assets/dialog-component-Dqg0QU9I.js.map +0 -1
  128. package/dist/assets/nostr-user-component-XEnanH-d.js +0 -2
  129. package/dist/assets/nostr-user-component-XEnanH-d.js.map +0 -1
  130. package/dist/assets/zap-utils-BZcaCsT_.js +0 -2
  131. package/dist/assets/zap-utils-BZcaCsT_.js.map +0 -1
package/README.md CHANGED
@@ -2,19 +2,19 @@
2
2
 
3
3
  **Embed Nostr anywhere on the internet, a Zap Button for every webpage.**
4
4
 
5
- ## 🚀 About the Project
5
+ ## About the Project
6
6
 
7
- Nostr Components makes it easy to embed Zap button, Nostr profiles, posts, and follow buttons in any website. Inspired by <a href="https://unpkg.com/nostr-web-components@0.0.15/demo.html">fiatjaf's Nostr Web Components</a>, this project adds a beautiful UI, a Storybook component generator (for webmasters), and allows embedding Nostr content anywhere on the Internet.
7
+ Nostr Components makes it easy to embed Zap button, Nostr profiles, posts, and follow buttons in any website. Inspired by <a href="https://unpkg.com/nostr-web-components@latest/demo.html">fiatjaf's Nostr Web Components</a>, this project adds a beautiful UI, a Storybook component generator (for webmasters), and allows embedding Nostr content anywhere on the Internet.
8
8
 
9
- 🔹 **[Zap button](#1-nostr-zap-)**
10
- 🔹 **[Follow button](#2-nostr-follow-)**
11
- 🔹 **[Like button](#3-nostr-like-)**
12
- 🔹 **[Profile Badge](#4-nostr-profile-badge-)**
13
- 🔹 **[Profile](#5-nostr-profile-)**
14
- 🔹 **[Post](#6-nostr-post-)**
15
- 🔹 **[WordPress Integration](#7-wordpress-integration)** - WordPress Integration
9
+ * **[Zap button](#1-nostr-zap)**
10
+ * **[Follow button](#2-nostr-follow)**
11
+ * **[Like button](#3-nostr-like)**
12
+ * **[Profile Badge](#4-nostr-profile-badge)**
13
+ * **[Profile](#5-nostr-profile)**
14
+ * **[Post](#6-nostr-post)**
15
+ * **[WordPress Integration](#7-wordpress-integration)**
16
16
 
17
- ## 🛠️ Usage
17
+ ## Usage
18
18
 
19
19
  ### Quick Start
20
20
 
@@ -25,14 +25,25 @@ Add the component script to your HTML's `<head>`. Each component can be loaded i
25
25
  ```html
26
26
  <head>
27
27
  <!-- Load individual component (recommended for smaller bundles) -->
28
- <script type="module" src="https://cdn.jsdelivr.net/npm/nostr-components@latest/dist/components/nostr-follow-button.es.js"></script>
28
+ <script type="module" src="https://cdn.jsdelivr.net/npm/nostr-components@latest/dist/components/nostr-profile.es.js"></script>
29
29
 
30
- <!-- Or load the full bundle -->
30
+ <!-- Or load the full bundle: ES -->
31
+ <script type="module" src="https://cdn.jsdelivr.net/npm/nostr-components@latest/dist/nostr-components.es.js"></script>
32
+
33
+ <!-- Or load the full bundle: UMD -->
31
34
  <script src="https://cdn.jsdelivr.net/npm/nostr-components@latest/dist/nostr-components.umd.js"></script>
32
- <script>
33
- NostrComponents.default.init();
34
- </script>
35
+
36
+ <!-- Optional: Dark theme -->
37
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nostr-components@0.3.2/dist/themes/dark.css">
35
38
  </head>
39
+ <body>
40
+ <h1> Welcome to My home page </h1>
41
+ <nostr-zap nip05="saiy2k@iris.to" url="https://nostr-components.web.app/"></nostr-zap>
42
+ <nostr-profile npub="npub1qsvv5ttv6mrlh38q8ydmw3gzwq360mdu8re2vr7rk68sqmhmsh4svhsft3"></nostr-profile>
43
+ <nostr-like></nostr-like>
44
+
45
+ <!-- For more components, see blow -->
46
+ </body>
36
47
  ```
37
48
 
38
49
  #### Option 2: NPM Package
@@ -47,30 +58,15 @@ Then import components in your JavaScript/TypeScript:
47
58
 
48
59
  ```javascript
49
60
  // Import individual components
50
- import 'nostr-components/dist/components/nostr-follow-button.es.js';
51
- import 'nostr-components/dist/components/nostr-zap.es.js';
52
- import 'nostr-components/dist/components/nostr-like.es.js';
61
+ // [Will update here soon]
53
62
 
54
63
  // Or import the full bundle
55
- import 'nostr-components/dist/nostr-components.es.js';
56
- ```
57
-
58
- For bundlers (Vite, Webpack, etc.), you can also import the source:
59
-
60
- ```javascript
61
- // Import from source (requires bundler)
62
- import { NostrFollowButton } from 'nostr-components';
63
- import { NostrZap } from 'nostr-components';
64
- import { NostrLike } from 'nostr-components';
64
+ import 'nostr-components';
65
65
  ```
66
66
 
67
- **Note:** When using npm packages, make sure your bundler is configured to handle Web Components properly.
68
-
69
- **Use the Components:** Place the component tags anywhere in your `<body>`.
70
-
71
67
  ### Authentication
72
68
 
73
- All interactive components (Follow, Like, Zap, DM, Live Chat, Comment) require user authentication. Components use [NostrLogin](https://github.com/nostrband/nostr-login) which supports:
69
+ All interactive components (Follow, Like, Zap) require user authentication. Components use [NostrLogin](https://github.com/nostrband/nostr-login) which supports:
74
70
  - **NIP-07 Browser Extensions** (Alby, nos2x, etc.)
75
71
  - **NIP-46 Remote Signers** (Bunkers)
76
72
 
@@ -78,20 +74,25 @@ The authentication flow is handled automatically when users interact with compon
78
74
 
79
75
  ---
80
76
 
81
- ## 1. Nostr Zap
77
+ ## 1. Nostr Zap
82
78
 
83
- A Lightning Network zap button that allows users to send sats to any Nostr user with a lightning address or LNURL.
79
+ A Zap button that allows users to send sats to any Nostr user or a URL associated with a User.
80
+ What is ~[https://www.youtube.com/shorts/PDnrh8pkF3g](Zap)?
84
81
 
85
82
  **Usage:**
86
83
 
87
84
  ```html
88
85
  <head>
89
- <script type="module" src="https://cdn.jsdelivr.net/npm/nostr-components@latest/dist/components/nostr-zap.es.js"></script>
86
+ <script
87
+ type="module"
88
+ src="https://cdn.jsdelivr.net/npm/nostr-components@latest/dist/components/nostr-zap.es.js">
89
+ </script>
90
90
  </head>
91
91
  <body>
92
92
  <nostr-zap
93
93
  npub="npub1qsvv5ttv6mrlh38q8ydmw3gzwq360mdu8re2vr7rk68sqmhmsh4svhsft3"
94
94
  theme="dark"
95
+ url="https://nostr-components.web.app/"
95
96
  text="⚡ Zap Me"
96
97
  ></nostr-zap>
97
98
  </body>
@@ -103,7 +104,7 @@ A Lightning Network zap button that allows users to send sats to any Nostr user
103
104
 
104
105
  ---
105
106
 
106
- ## 2. Nostr Follow
107
+ ## 2. Nostr Follow
107
108
 
108
109
  A simple button that allows users to follow a Nostr profile.
109
110
 
@@ -129,7 +130,7 @@ A simple button that allows users to follow a Nostr profile.
129
130
 
130
131
  ---
131
132
 
132
- ## 3. Nostr Like ❤️
133
+ ## 3. Nostr Like
133
134
 
134
135
  A like button that uses NIP-25 (External Content Reactions) to like any URL on the web.
135
136
  When URL is not specified, current URL is taken.
@@ -138,14 +139,20 @@ When URL is not specified, current URL is taken.
138
139
 
139
140
  ```html
140
141
  <head>
141
- <script type="module" src="https://cdn.jsdelivr.net/npm/nostr-components@latest/dist/components/nostr-like.es.js"></script>
142
+ <script
143
+ type="module"
144
+ src="https://cdn.jsdelivr.net/npm/nostr-components@latest/dist/components/nostr-like.es.js">
145
+ </script>
142
146
  </head>
143
147
  <body>
144
148
  <!-- Like the current page URL -->
145
149
  <nostr-like></nostr-like>
146
150
 
147
151
  <!-- Like a specific URL with custom text -->
148
- <nostr-like url="https://github.com/saiy2k/nostr-components" text="❤️"></nostr-like>
152
+ <nostr-like
153
+ url="https://github.com/saiy2k/nostr-components"
154
+ text="❤️">
155
+ </nostr-like>
149
156
  </body>
150
157
  ```
151
158
 
@@ -155,7 +162,7 @@ When URL is not specified, current URL is taken.
155
162
 
156
163
  ---
157
164
 
158
- ## 4. Nostr Profile Badge 🔖
165
+ ## 4. Nostr Profile Badge
159
166
 
160
167
  A small badge displaying a Nostr profile with a username and avatar.
161
168
 
@@ -181,7 +188,7 @@ A small badge displaying a Nostr profile with a username and avatar.
181
188
 
182
189
  ---
183
190
 
184
- ## 5. Nostr Profile 👤
191
+ ## 5. Nostr Profile
185
192
 
186
193
  A detailed profile card showing avatar, name, bio, notes count, followers, etc.
187
194
 
@@ -189,7 +196,10 @@ A detailed profile card showing avatar, name, bio, notes count, followers, etc.
189
196
 
190
197
  ```html
191
198
  <head>
192
- <script type="module" src="https://cdn.jsdelivr.net/npm/nostr-components@latest/dist/components/nostr-profile.es.js"></script>
199
+ <script
200
+ type="module"
201
+ src="https://cdn.jsdelivr.net/npm/nostr-components@latest/dist/components/nostr-profile.es.js">
202
+ </script>
193
203
  </head>
194
204
  <body>
195
205
  <nostr-profile
@@ -204,7 +214,7 @@ A detailed profile card showing avatar, name, bio, notes count, followers, etc.
204
214
 
205
215
  ---
206
216
 
207
- ## 6. Nostr Post 📝
217
+ ## 6. Nostr Post
208
218
 
209
219
  Embed any Nostr post by providing the event ID.
210
220
 
@@ -212,7 +222,9 @@ Embed any Nostr post by providing the event ID.
212
222
 
213
223
  ```html
214
224
  <head>
215
- <script type="module" src="https://cdn.jsdelivr.net/npm/nostr-components@latest/dist/components/nostr-post.es.js"></script>
225
+ <script
226
+ type="module"
227
+ src="https://cdn.jsdelivr.net/npm/nostr-components@latest/dist/components/nostr-post.es.js"></script>
216
228
  </head>
217
229
  <body>
218
230
  <nostr-post
@@ -227,7 +239,7 @@ Embed any Nostr post by providing the event ID.
227
239
 
228
240
  ---
229
241
 
230
- ## 7. WordPress Integration 🔌
242
+ ## 7. WordPress Integration
231
243
 
232
244
  The Nostr Components WordPress plugin provides Gutenberg blocks and shortcodes for all components, making it easy to embed Nostr functionality in your WordPress site.
233
245
 
@@ -1,4 +1,4 @@
1
- var w=Object.defineProperty;var k=(t,r,e)=>r in t?w(t,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[r]=e;var c=(t,r,e)=>k(t,typeof r!="symbol"?r+"":r,e);import{D as v,c as p,g as m,M as E,N as T}from"./nostr-service-m3Hgc5Xx.js";const I=t=>{if(typeof t!="string"||!t.startsWith("npub1"))return"";try{const r=p.decode(t);if(r&&typeof r.data=="string")return r.data}catch(r){console.error("Failed to decode npub:",r)}return""};function M(t){if(!t||!N(t))return"";try{return p.npubEncode(t.toLowerCase())}catch(r){return console.error("Failed to encode hex to npub:",r),""}}function O(t="",r=3){const e=t.length;if(!t.startsWith("npub1"))return"Invalid nPub: expected npub1...";if(!S(t))return"Invalid nPub";let s="npub1";for(let n=5;n<r+5;n++)s+=t[n];s+="...";let o="";for(let n=e-1;n>=e-r;n--)o=t[n]+o;return s+=o,s}async function z(t,r){const e=await t.fetchEvents({kinds:[m.Repost],"#e":[r||""]}),s=d=>d.tags.filter(g=>g[0]==="p").length===1,o=d=>d.tags.filter(g=>g[0]==="e").length===1,n=Array.from(e).filter(s).length,a=await t.fetchEvents({kinds:[m.Reaction],"#e":[r||""]}),i=0,u=await t.fetchEvents({kinds:[m.Text],"#e":[r||""]}),l=Array.from(u).filter(o).length;return{likes:a.size,reposts:n,zaps:i/E,replies:l}}function C(t){if(t){const r=t.split(",").map(e=>e.trim()).filter(Boolean).filter(y);return r.length?Array.from(new Set(r)):[...v]}return[...v]}function L(t){const r=t==null?void 0:t.trim().toLowerCase();return r==="light"||r==="dark"?r:"light"}function P(t){return t===null?!1:t===""||t.toLowerCase()==="true"}function B(t){const r=document.createElement("div");return r.textContent=t,r.innerHTML}function F(t){try{const r=new URL(t);return["http:","https:"].includes(r.protocol)}catch{return!1}}function y(t){try{const r=new URL(t);return r.protocol==="wss:"||r.protocol==="ws:"}catch{return!1}}function N(t){return/^[0-9a-fA-F]+$/.test(t)&&t.length===64}function S(t){try{const{type:r}=p.decode(t);return r==="npub"}catch{return!1}}function j(t){return/^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+\.[a-zA-Z]{2,}$/.test(t)}function b(t,r){try{const{type:e}=p.decode(t);return e===r}catch{return!1}}function U(t){return b(t,"note")}function D(t){return b(t,"nevent")}function H(t){return navigator.clipboard.writeText(t)}function q(t){try{const r=Date.now(),e=t*1e3,s=r-e,o=Math.floor(s/1e3);if(o<60)return"just now";if(o<3600){const a=Math.floor(o/60);return`${a} ${a===1?"min":"mins"} ago`}if(o<86400){const a=Math.floor(o/3600);return`${a} ${a===1?"hour":"hours"} ago`}if(o<2592e3){const a=Math.floor(o/86400);return`${a} ${a===1?"day":"days"} ago`}if(o<31536e3){const a=Math.floor(o/2592e3);return`${a} ${a===1?"month":"months"} ago`}const n=Math.floor(o/31536e3);return`${n} ${n===1?"year":"years"} ago`}catch(r){return console.error("Error formatting relative time:",r),"unknown"}}var f=(t=>(t[t.Idle=0]="Idle",t[t.Loading=1]="Loading",t[t.Ready=2]="Ready",t[t.Error=3]="Error",t))(f||{});const $="nc:status";class K extends HTMLElement{constructor(e=!0){super();c(this,"nostrService",T.getInstance());c(this,"theme","light");c(this,"errorMessage","");c(this,"nostrReady");c(this,"nostrReadyResolve");c(this,"nostrReadyReject");c(this,"conn",this.channel("connection"));c(this,"_statuses",new Map);c(this,"_overall",0);c(this,"connectSeq",0);e&&this.attachShadow({mode:"open"}),this.resetNostrReadyBarrier()}static get observedAttributes(){return["data-theme","relays"]}connectedCallback(){this.validateInputs()&&(this.getTheme(),this.conn.get()===0&&this.connectToNostr())}disconnectedCallback(){this.shadowRoot&&this._delegated&&this._delegated.clear()}attributeChangedCallback(e,s,o){s!==o&&(e==="data-theme"||e==="relays")&&this.validateInputs()&&(e==="relays"&&(this.resetNostrReadyBarrier(),this.connectToNostr()),e==="data-theme"&&(this.getTheme(),this.render()))}setStatusFor(e,s,o){const n=this._statuses.get(e);if(!(n!==s||s===3&&!!o))return;this._statuses.set(e,s),s===3&&o?this.errorMessage=o:n===3&&s!==3&&(this.errorMessage="");const i=`${e}-status`,u=f[s].toLowerCase();this.getAttribute(i)!==u&&this.setAttribute(i,u);const l=this.computeOverall(),d=f[l].toLowerCase();this._overall!==l?(this._overall=l,this.setAttribute("status",d),this.onStatusChange(l)):l===3&&o&&this.onStatusChange(l),this.dispatchEvent(new CustomEvent($,{detail:{key:e,status:s,all:this.snapshotStatuses(),overall:this._overall,errorMessage:this.errorMessage||void 0},bubbles:!0,composed:!0}))}getStatusFor(e){return this._statuses.get(e)??0}snapshotStatuses(){return Object.fromEntries(this._statuses.entries())}onStatusChange(e){}onNostrRelaysConnected(){}computeOverall(){const e=[...this._statuses.values()];return e.includes(3)?3:e.includes(1)?1:e.includes(2)?2:0}initChannelStatus(e,s,o={reflectOverall:!1}){if(this._statuses.set(e,s),this.setAttribute(`${e}-status`,f[s].toLowerCase()),o.reflectOverall){const n=this.computeOverall();this._overall=n,this.setAttribute("status",f[n].toLowerCase())}}channel(e){return{set:(s,o)=>this.setStatusFor(e,s,o),get:()=>this.getStatusFor(e)}}validateInputs(){const e=this.getAttribute("data-theme")||"light",s=this.getAttribute("relays"),o=this.tagName.toLowerCase();if(e==="light"||e==="dark"){if(s&&typeof s!="string")return this.conn.set(3,"Invalid relays list"),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1;if(s){const a=s.split(",").map(i=>i.trim()).filter(Boolean).filter(i=>!y(i));if(a.length>0){const i=a.join(", ");return this.conn.set(3,`Invalid relay URLs: ${i}. Relay URLs must start with 'wss://' or 'ws://'`),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1}}}else return this.conn.set(3,`Invalid theme '${e}'. Accepted values are 'light', 'dark'`),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1;return this.errorMessage="",!0}async connectToNostr(){var s,o;const e=++this.connectSeq;this.conn.set(1);try{if(await this.nostrService.connectToNostr(this.getRelays()),e!==this.connectSeq)return;this.conn.set(2),(s=this.nostrReadyResolve)==null||s.call(this);try{this.onNostrRelaysConnected()}catch(n){console.error("Error in onNostrRelaysConnected hook:",n)}}catch(n){if(e!==this.connectSeq)return;console.error("Failed to connect to Nostr relays:",n),this.conn.set(3,"Failed to connect to relays"),(o=this.nostrReadyReject)==null||o.call(this,n)}}ensureNostrConnected(){return this.nostrReady}getRelays(){return C(this.getAttribute("relays"))}getTheme(){this.theme=L(this.getAttribute("data-theme"))}delegateEvent(e,s,o){var i;const n=this.shadowRoot;if(!n)return;const a=`${e}:${s}`;(i=this._delegated)!=null&&i.has(a)||(this._delegated||(this._delegated=new Set),this._delegated.add(a),n.addEventListener(e,u=>{u.target.closest(s)&&o(u)}))}addDelegatedListener(e,s,o){this.delegateEvent(e,s,o)}renderError(e){return`Error: ${e}`}updateHostClasses(){const e=this.computeOverall()===1,s=this.computeOverall()===3,o=this.computeOverall()===2;this.classList.remove("is-clickable","is-disabled","is-error"),e?this.classList.add("is-disabled"):s?this.classList.add("is-error"):o&&this.classList.add("is-clickable")}render(){this.updateHostClasses(),this.renderContent()}handleNjumpClick(e,s,o){if(this.computeOverall()!==2)return;const n=new CustomEvent(e,{detail:s,bubbles:!0,composed:!0,cancelable:!0});this.dispatchEvent(n)&&window.open(`https://njump.me/${o}`,"_blank","noopener,noreferrer")}resetNostrReadyBarrier(){this.connectSeq++,this.nostrReady=new Promise((e,s)=>{this.nostrReadyResolve=e,this.nostrReadyReject=s})}}function x(){return`
1
+ var w=Object.defineProperty;var k=(t,r,e)=>r in t?w(t,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[r]=e;var c=(t,r,e)=>k(t,typeof r!="symbol"?r+"":r,e);import{c as v,a as p,e as m,M as E,b as T}from"./nostr-service-CA0Qx4nJ.js";const I=t=>{if(typeof t!="string"||!t.startsWith("npub1"))return"";try{const r=p.decode(t);if(r&&typeof r.data=="string")return r.data}catch(r){console.error("Failed to decode npub:",r)}return""};function M(t){if(!t||!S(t))return"";try{return p.npubEncode(t.toLowerCase())}catch(r){return console.error("Failed to encode hex to npub:",r),""}}function O(t="",r=3){const e=t.length;if(!t.startsWith("npub1"))return"Invalid nPub: expected npub1...";if(!N(t))return"Invalid nPub";let s="npub1";for(let n=5;n<r+5;n++)s+=t[n];s+="...";let o="";for(let n=e-1;n>=e-r;n--)o=t[n]+o;return s+=o,s}async function z(t,r){const e=await t.fetchEvents({kinds:[m.Repost],"#e":[r||""]}),s=d=>d.tags.filter(g=>g[0]==="p").length===1,o=d=>d.tags.filter(g=>g[0]==="e").length===1,n=Array.from(e).filter(s).length,a=await t.fetchEvents({kinds:[m.Reaction],"#e":[r||""]}),i=0,u=await t.fetchEvents({kinds:[m.Text],"#e":[r||""]}),l=Array.from(u).filter(o).length;return{likes:a.size,reposts:n,zaps:i/E,replies:l}}function C(t){if(t){const r=t.split(",").map(e=>e.trim()).filter(Boolean).filter(y);return r.length?Array.from(new Set(r)):[...v]}return[...v]}function L(t){const r=t==null?void 0:t.trim().toLowerCase();return r==="light"||r==="dark"?r:"light"}function P(t){return t===null?!1:t===""||t.toLowerCase()==="true"}function B(t){const r=document.createElement("div");return r.textContent=t,r.innerHTML}function F(t){try{const r=new URL(t);return["http:","https:"].includes(r.protocol)}catch{return!1}}function y(t){try{const r=new URL(t);return r.protocol==="wss:"||r.protocol==="ws:"}catch{return!1}}function S(t){return/^[0-9a-fA-F]+$/.test(t)&&t.length===64}function N(t){try{const{type:r}=p.decode(t);return r==="npub"}catch{return!1}}function j(t){return/^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+\.[a-zA-Z]{2,}$/.test(t)}function b(t,r){try{const{type:e}=p.decode(t);return e===r}catch{return!1}}function U(t){return b(t,"note")}function D(t){return b(t,"nevent")}function H(t){return navigator.clipboard.writeText(t)}function q(t){try{const r=Date.now(),e=t*1e3,s=r-e,o=Math.floor(s/1e3);if(o<60)return"just now";if(o<3600){const a=Math.floor(o/60);return`${a} ${a===1?"min":"mins"} ago`}if(o<86400){const a=Math.floor(o/3600);return`${a} ${a===1?"hour":"hours"} ago`}if(o<2592e3){const a=Math.floor(o/86400);return`${a} ${a===1?"day":"days"} ago`}if(o<31536e3){const a=Math.floor(o/2592e3);return`${a} ${a===1?"month":"months"} ago`}const n=Math.floor(o/31536e3);return`${n} ${n===1?"year":"years"} ago`}catch(r){return console.error("Error formatting relative time:",r),"unknown"}}var f=(t=>(t[t.Idle=0]="Idle",t[t.Loading=1]="Loading",t[t.Ready=2]="Ready",t[t.Error=3]="Error",t))(f||{});const $="nc:status";class K extends HTMLElement{constructor(e=!0){super();c(this,"nostrService",T.getInstance());c(this,"theme","light");c(this,"errorMessage","");c(this,"nostrReady");c(this,"nostrReadyResolve");c(this,"nostrReadyReject");c(this,"conn",this.channel("connection"));c(this,"_statuses",new Map);c(this,"_overall",0);c(this,"connectSeq",0);e&&this.attachShadow({mode:"open"}),this.resetNostrReadyBarrier()}static get observedAttributes(){return["data-theme","relays"]}connectedCallback(){this.validateInputs()&&(this.getTheme(),this.conn.get()===0&&this.connectToNostr())}disconnectedCallback(){this.shadowRoot&&this._delegated&&this._delegated.clear()}attributeChangedCallback(e,s,o){s!==o&&(e==="data-theme"||e==="relays")&&this.validateInputs()&&(e==="relays"&&(this.resetNostrReadyBarrier(),this.connectToNostr()),e==="data-theme"&&(this.getTheme(),this.render()))}setStatusFor(e,s,o){const n=this._statuses.get(e);if(!(n!==s||s===3&&!!o))return;this._statuses.set(e,s),s===3&&o?this.errorMessage=o:n===3&&s!==3&&(this.errorMessage="");const i=`${e}-status`,u=f[s].toLowerCase();this.getAttribute(i)!==u&&this.setAttribute(i,u);const l=this.computeOverall(),d=f[l].toLowerCase();this._overall!==l?(this._overall=l,this.setAttribute("status",d),this.onStatusChange(l)):l===3&&o&&this.onStatusChange(l),this.dispatchEvent(new CustomEvent($,{detail:{key:e,status:s,all:this.snapshotStatuses(),overall:this._overall,errorMessage:this.errorMessage||void 0},bubbles:!0,composed:!0}))}getStatusFor(e){return this._statuses.get(e)??0}snapshotStatuses(){return Object.fromEntries(this._statuses.entries())}onStatusChange(e){}onNostrRelaysConnected(){}computeOverall(){const e=[...this._statuses.values()];return e.includes(3)?3:e.includes(1)?1:e.includes(2)?2:0}initChannelStatus(e,s,o={reflectOverall:!1}){if(this._statuses.set(e,s),this.setAttribute(`${e}-status`,f[s].toLowerCase()),o.reflectOverall){const n=this.computeOverall();this._overall=n,this.setAttribute("status",f[n].toLowerCase())}}channel(e){return{set:(s,o)=>this.setStatusFor(e,s,o),get:()=>this.getStatusFor(e)}}validateInputs(){const e=this.getAttribute("data-theme")||"light",s=this.getAttribute("relays"),o=this.tagName.toLowerCase();if(e==="light"||e==="dark"){if(s&&typeof s!="string")return this.conn.set(3,"Invalid relays list"),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1;if(s){const a=s.split(",").map(i=>i.trim()).filter(Boolean).filter(i=>!y(i));if(a.length>0){const i=a.join(", ");return this.conn.set(3,`Invalid relay URLs: ${i}. Relay URLs must start with 'wss://' or 'ws://'`),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1}}}else return this.conn.set(3,`Invalid theme '${e}'. Accepted values are 'light', 'dark'`),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1;return this.errorMessage="",!0}async connectToNostr(){var s,o;const e=++this.connectSeq;this.conn.set(1);try{if(await this.nostrService.connectToNostr(this.getRelays()),e!==this.connectSeq)return;this.conn.set(2),(s=this.nostrReadyResolve)==null||s.call(this);try{this.onNostrRelaysConnected()}catch(n){console.error("Error in onNostrRelaysConnected hook:",n)}}catch(n){if(e!==this.connectSeq)return;console.error("Failed to connect to Nostr relays:",n),this.conn.set(3,"Failed to connect to relays"),(o=this.nostrReadyReject)==null||o.call(this,n)}}ensureNostrConnected(){return this.nostrReady}getRelays(){return C(this.getAttribute("relays"))}getTheme(){this.theme=L(this.getAttribute("data-theme"))}delegateEvent(e,s,o){var i;const n=this.shadowRoot;if(!n)return;const a=`${e}:${s}`;(i=this._delegated)!=null&&i.has(a)||(this._delegated||(this._delegated=new Set),this._delegated.add(a),n.addEventListener(e,u=>{u.target.closest(s)&&o(u)}))}addDelegatedListener(e,s,o){this.delegateEvent(e,s,o)}renderError(e){return`Error: ${e}`}updateHostClasses(){const e=this.computeOverall()===1,s=this.computeOverall()===3,o=this.computeOverall()===2;this.classList.remove("is-clickable","is-disabled","is-error"),e?this.classList.add("is-disabled"):s?this.classList.add("is-error"):o&&this.classList.add("is-clickable")}render(){this.updateHostClasses(),this.renderContent()}handleNjumpClick(e,s,o){if(this.computeOverall()!==2)return;const n=new CustomEvent(e,{detail:s,bubbles:!0,composed:!0,cancelable:!0});this.dispatchEvent(n)&&window.open(`https://njump.me/${o}`,"_blank","noopener,noreferrer")}resetNostrReadyBarrier(){this.connectSeq++,this.nostrReady=new Promise((e,s)=>{this.nostrReadyResolve=e,this.nostrReadyReject=s})}}function x(){return`
2
2
  :host {
3
3
  /* === GENERIC DESIGN TOKENS === */
4
4
  --nostrc-color-background: #ffffff;
@@ -141,5 +141,5 @@ var w=Object.defineProperty;var k=(t,r,e)=>r in t?w(t,r,{enumerable:!0,configura
141
141
  font-size: 2em;
142
142
  margin: auto;
143
143
  }
144
- `};export{K as N,f as a,S as b,F as c,H as d,B as e,q as f,V as g,M as h,N as i,U as j,D as k,z as l,O as m,I as n,P as p,j as v};
145
- //# sourceMappingURL=base-styles-DC0ilu4S.js.map
144
+ `};export{f as N,S as a,D as b,K as c,z as d,B as e,H as f,V as g,I as h,F as i,M as j,q as k,j as l,O as m,N as n,P as p,U as v};
145
+ //# sourceMappingURL=base-styles-Dmuzg8I4.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-styles-DC0ilu4S.js","sources":["../../src/common/utils.ts","../../src/base/base-component/nostr-base-component.ts","../../src/common/base-styles.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport NDK, { NDKKind, NDKEvent } from '@nostr-dev-kit/ndk';\nimport { nip19 } from \"nostr-tools\";\n\n\nimport { Theme } from './types';\nimport { DEFAULT_RELAYS, MILLISATS_PER_SAT } from './constants';\n\nexport const decodeNpub = (npub: string): string => {\n if (typeof npub !== 'string' || !npub.startsWith('npub1')) {\n return '';\n }\n\n try {\n const decoded = nip19.decode(npub);\n if (decoded && typeof decoded.data === 'string') {\n return decoded.data;\n }\n } catch (error) {\n console.error('Failed to decode npub:', error);\n }\n \n return '';\n};\n\n/**\n * Convert hex pubkey to npub\n */\nexport function hexToNpub(hex: string): string {\n if (!hex || !isValidHex(hex)) return '';\n try {\n return nip19.npubEncode(hex.toLowerCase());\n } catch (error) {\n console.error('Failed to encode hex to npub:', error);\n return '';\n }\n}\n\n// Could be npub, note1, naddr, nsec, etc.,\nexport const decodeNip19Entity = (entity: string): any => {\n if (typeof entity !== 'string' || !/^[a-z0-9]+1[ac-hj-np-z02-9]+/.test(entity)) {\n return null;\n }\n\n try {\n const decoded = nip19.decode(entity);\n return decoded?.data ?? null;\n } catch (error) {\n console.error('Failed to decode NIP-19 entity:', error);\n return null;\n }\n};\n\nexport function maskNPub(npubString: string = '', length = 3) {\n const npubLength = npubString.length;\n\n if (!npubString.startsWith('npub1')) {\n return 'Invalid nPub: expected npub1...';\n }\n\n if (!validateNpub(npubString)) {\n return 'Invalid nPub';\n }\n\n let result = 'npub1';\n\n for (let i = 5; i < length + 5; i++) {\n result += npubString[i];\n }\n\n result += '...';\n\n let suffix = '';\n for (let i = npubLength - 1; i >= npubLength - length; i--) {\n suffix = npubString[i] + suffix;\n }\n\n result += suffix;\n\n return result;\n}\n\nexport type Stats = {\n likes: number;\n reposts: number;\n zaps: number;\n replies: number;\n};\n\nexport async function getPostStats(ndk: NDK, postId: string): Promise<Stats> {\n const reposts = await ndk.fetchEvents({\n kinds: [NDKKind.Repost],\n '#e': [postId || ''],\n });\n\n const isDirectRepost = (repost: NDKEvent): boolean => {\n const pTagCounts = repost.tags.filter(tag => tag[0] === 'p').length;\n return pTagCounts === 1;\n };\n\n const isDirectReply = (reply: NDKEvent): boolean => {\n const eTagsCount = reply.tags.filter(tag => tag[0] === 'e').length;\n return eTagsCount === 1;\n };\n\n // Only take the count of direct reposts\n const repostsCount = Array.from(reposts).filter(isDirectRepost).length;\n\n const likes = await ndk.fetchEvents({\n kinds: [NDKKind.Reaction],\n '#e': [postId || ''],\n });\n\n // TODO: Add zap receipt validation - https://github.com/nostr-protocol/nips/blob/master/57.md#appendix-f-validating-zap-receipts\n // const zaps = await ndk.fetchEvents({\n // kinds: [NDKKind.Zap],\n // '#e': [postId || '']\n // });\n\n // const zapAmount = Array.from(zaps).reduce((prev, curr) => {\n // const bolt11Tag = curr.getMatchingTags('bolt11');\n\n // if(\n // !bolt11Tag ||\n // !Array.isArray(bolt11Tag) ||\n // bolt11Tag.length === 0 ||\n // !bolt11Tag[0] ||\n // !Array.isArray(bolt11Tag[0]) ||\n // (bolt11Tag[0] as string[]).length === 0\n // ) {\n // return prev;\n // }\n\n // const bolt11 = bolt11Tag[0][1];\n\n // const decodedbol11 = decode(bolt11);\n\n // const amountSection = decodedbol11.sections.find(section => section.name === 'amount');\n\n // if(amountSection) {\n // const millisats = Number(amountSection.value);\n\n // return prev + millisats;\n // }\n\n // return prev;\n // }, 0);\n\n const zapAmount = 0;\n\n const replies = await ndk.fetchEvents({\n kinds: [NDKKind.Text],\n '#e': [postId || ''],\n });\n\n // Only take the direct replies\n // https://github.com/nostr-protocol/nips/blob/master/10.md#positional-e-tags-deprecated\n const replyCount = Array.from(replies).filter(isDirectReply).length;\n\n return {\n likes: likes.size,\n reposts: repostsCount,\n zaps: zapAmount / MILLISATS_PER_SAT,\n replies: replyCount,\n };\n}\n\nexport function parseRelays(relaysAttr: string | null): string[] {\n if (relaysAttr) {\n const list = relaysAttr\n .split(',')\n .map(r => r.trim())\n .filter(Boolean)\n .filter(isValidRelayUrl);\n // fall back to defaults if user provided no valid entries\n return list.length ? Array.from(new Set(list)) : [...DEFAULT_RELAYS];\n }\n return [...DEFAULT_RELAYS];\n}\n\nexport function parseTheme(themeAttr: string | null): Theme {\n\n const theme = themeAttr?.trim().toLowerCase();\n\n if (theme === 'light' || theme === 'dark') {\n return theme;\n }\n\n return 'light';\n}\n\nexport function parseBooleanAttribute(attr: string | null): boolean {\n // Handles: \"true\", \"\", null, \"false\"\n if (attr === null) return false;\n if (attr === '' || attr.toLowerCase() === 'true') return true;\n return false;\n}\n\nexport function escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n\nexport function isValidUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return ['http:', 'https:'].includes(parsed.protocol);\n } catch {\n return false;\n }\n}\n\nexport function isValidRelayUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return u.protocol === 'wss:' || u.protocol === 'ws:';\n } catch {\n return false;\n }\n}\n\nexport function isValidHex(hex: string): boolean {\n return /^[0-9a-fA-F]+$/.test(hex) && hex.length === 64;\n}\n\nexport function validateNpub(npub: string): boolean {\n try {\n const { type } = nip19.decode(npub);\n return type === 'npub';\n } catch (e) {\n return false;\n }\n}\n\nexport function validateNip05(nip05: string): boolean {\n const nip05Regex = /^[a-zA-Z0-9_\\-\\.]+@[a-zA-Z0-9_\\-\\.]+\\.[a-zA-Z]{2,}$/;\n return nip05Regex.test(nip05);\n}\n\nfunction validateBech32OfType(input: string, expected: 'note' | 'nevent'): boolean {\n try {\n const { type } = nip19.decode(input);\n return type === expected;\n } catch {\n return false;\n }\n}\n\nexport function validateNoteId(noteId: string): boolean {\n return validateBech32OfType(noteId, 'note');\n}\n\nexport function validateEventId(eventId: string): boolean {\n return validateBech32OfType(eventId, 'nevent');\n}\n\nexport function copyToClipboard(text: string): Promise<void> {\n return navigator.clipboard.writeText(text)\n}\n\n/**\n * Format timestamp as relative time (e.g., \"2 mins ago\", \"1 month ago\")\n * @param ts Timestamp in seconds\n * @returns Formatted relative time string\n */\nexport function formatRelativeTime(ts: number): string {\n try {\n const now = Date.now();\n const messageTime = ts * 1000;\n const diffMs = now - messageTime;\n\n // Convert to seconds\n const diffSec = Math.floor(diffMs / 1000);\n\n if (diffSec < 60) {\n return 'just now';\n }\n\n // Minutes\n if (diffSec < 3600) {\n const mins = Math.floor(diffSec / 60);\n return `${mins} ${mins === 1 ? 'min' : 'mins'} ago`;\n }\n\n // Hours\n if (diffSec < 86400) {\n const hours = Math.floor(diffSec / 3600);\n return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;\n }\n\n // Days\n if (diffSec < 2592000) { // ~30 days\n const days = Math.floor(diffSec / 86400);\n return `${days} ${days === 1 ? 'day' : 'days'} ago`;\n }\n\n // Months\n if (diffSec < 31536000) { // ~365 days\n const months = Math.floor(diffSec / 2592000);\n return `${months} ${months === 1 ? 'month' : 'months'} ago`;\n }\n\n // Years\n const years = Math.floor(diffSec / 31536000);\n return `${years} ${years === 1 ? 'year' : 'years'} ago`;\n } catch (error) {\n console.error('Error formatting relative time:', error);\n return 'unknown';\n }\n}\n","// SPDX-License-Identifier: MIT\n\nimport { NostrService } from '../../common/nostr-service';\nimport { Theme } from '../../common/types';\nimport { parseRelays, parseTheme, isValidRelayUrl } from '../../common/utils';\n\nexport enum NCStatus {\n Idle, // 0\n Loading, // 1\n Ready, // 2\n Error, // 3\n}\n\nconst EVT_STATUS = 'nc:status';\n\n/**\n * NostrBaseComponent\n * ==================\n * Foundation for all Nostr web components in this library.\n *\n * Overview\n * - Manages relay connectivity via a shared `NostrService`.\n * - Parses common attributes (`theme`, `relays`) and applies theme.\n * - Provides a generic status management logic, that is extensible by derived classes.\n * - Offers small utilities useful to subclasses (event delegation, error snippet).\n *\n * Observed attributes\n * - `data-theme` — \"light\" | \"dark\"\n * - `relays` — CSV of relay URLs\n * \n * Events\n * - `nc:status` — from base, reflects connection and user/profile loading status\n * \n * TODO: Is this class doing too much work? Time to split into smaller components?\n */\nexport abstract class NostrBaseComponent extends HTMLElement {\n\n protected nostrService: NostrService = NostrService.getInstance();\n\n protected theme: Theme = 'light';\n protected errorMessage: string = '';\n\n protected nostrReady!: Promise<void>;\n protected nostrReadyResolve?: () => void;\n protected nostrReadyReject?: (e: unknown) => void;\n\n protected conn = this.channel('connection');\n\n private _statuses = new Map<string, NCStatus>();\n private _overall: NCStatus = NCStatus.Idle;\n\n // guard to ignore stale connects\n private connectSeq = 0;\n\n constructor(shadow: boolean = true) {\n super();\n if (shadow) this.attachShadow({ mode: 'open' });\n this.resetNostrReadyBarrier();\n }\n\n /** Lifecycle methods */\n static get observedAttributes() {\n return ['data-theme', 'relays'];\n }\n\n connectedCallback() {\n if (this.validateInputs()) {\n this.getTheme();\n // Avoid duplicate connects if a subclass handles it\n if (this.conn.get() === NCStatus.Idle) {\n void this.connectToNostr();\n }\n }\n }\n\n disconnectedCallback() {\n // Clean up delegated event listeners if shadow root exists\n if (this.shadowRoot && (this as any)._delegated) {\n (this as any)._delegated.clear();\n }\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null\n ) {\n if (oldValue === newValue) return;\n\n if (name === 'data-theme' || name === 'relays') {\n if (this.validateInputs()) {\n if (name === 'relays') {\n this.resetNostrReadyBarrier();\n void this.connectToNostr();\n }\n\n if (name === 'data-theme') {\n this.getTheme();\n this.render();\n }\n }\n }\n }\n\n /** Status map API */\n\n protected setStatusFor(key: string, next: NCStatus, error?: string) {\n const prev = this._statuses.get(key);\n const changed = prev !== next || (next === NCStatus.Error && !!error);\n\n if (!changed) return;\n\n this._statuses.set(key, next);\n\n if (next === NCStatus.Error && error) {\n this.errorMessage = error;\n } else if (prev === NCStatus.Error && next !== NCStatus.Error) {\n this.errorMessage = '';\n }\n\n // Reflect per-key attribute, e.g. user-status=\"loading\"\n const perKeyAttr = `${key}-status`;\n const perKeyVal = NCStatus[next].toLowerCase();\n if (this.getAttribute(perKeyAttr) !== perKeyVal) {\n this.setAttribute(perKeyAttr, perKeyVal);\n }\n\n // Compute & reflect overall for backward-compat CSS\n const overall = this.computeOverall();\n const overallVal = NCStatus[overall].toLowerCase();\n if (this._overall !== overall) {\n this._overall = overall;\n this.setAttribute('status', overallVal);\n this.onStatusChange(overall);\n } else if (overall === NCStatus.Error && error) {\n // propagate error updates even if overall state didn't flip\n this.onStatusChange(overall);\n }\n\n // Emit a single event with structured detail\n this.dispatchEvent(new CustomEvent(EVT_STATUS, {\n detail: {\n key,\n status: next,\n all: this.snapshotStatuses(),\n overall: this._overall,\n errorMessage: this.errorMessage || undefined,\n },\n bubbles: true,\n composed: true,\n }));\n }\n\n protected getStatusFor(key: string): NCStatus {\n return this._statuses.get(key) ?? NCStatus.Idle;\n }\n\n protected snapshotStatuses(): Record<string, NCStatus> {\n return Object.fromEntries(this._statuses.entries());\n }\n\n /** Overall status change hook */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n protected onStatusChange(_overall: NCStatus) { }\n\n /** Hook for subclasses to react when relays are connected */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n protected onNostrRelaysConnected() { }\n\n protected computeOverall(): NCStatus {\n const vals = [...this._statuses.values()];\n if (vals.includes(NCStatus.Error)) return NCStatus.Error;\n if (vals.includes(NCStatus.Loading)) return NCStatus.Loading;\n if (vals.includes(NCStatus.Ready)) return NCStatus.Ready;\n return NCStatus.Idle;\n }\n\n /**\n * Before first channel.set(), the map won't be set and hence get() will return `idle`.\n * In some cases (or maybe in all cases), this is not ideal.\n * In UserComponent, 'user' not set and hence returning `idle`, while\n * 'connect' switched between idle -> loading -> ready\n * and then\n * 'user' switches to loading -> ready,\n * results in loading -> ready -> loading -> ready in profile-badge onStatusChange.\n * \n * To avoid this, this function is called from UserComponent constructor,\n * to set default value as 'loading' without emitting onStatusChange event.\n * \n * Makes sense?\n * Try commenting this function call in UserComponent()\n * and add a log in ProfileBadge :: onStatusChange. You will get it.\n */\n protected initChannelStatus(key: string, status: NCStatus, opts = { reflectOverall: false }) {\n this._statuses.set(key, status);\n this.setAttribute(`${key}-status`, NCStatus[status].toLowerCase());\n if (opts.reflectOverall) {\n const overall = this.computeOverall();\n this._overall = overall;\n this.setAttribute('status', NCStatus[overall].toLowerCase());\n }\n }\n\n protected channel(key: string) {\n return {\n set: (s: NCStatus, e?: string) => this.setStatusFor(key, s, e),\n get: () => this.getStatusFor(key)\n };\n }\n\n /** Protected methods */\n protected validateInputs(): boolean {\n const theme = this.getAttribute('data-theme') || 'light';\n const relays = this.getAttribute('relays');\n const tagName = this.tagName.toLowerCase();\n\n if (!(theme === 'light' || theme === 'dark')) {\n this.conn.set(NCStatus.Error, `Invalid theme '${theme}'. Accepted values are 'light', 'dark'`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n } else if (relays && typeof relays != 'string') {\n this.conn.set(NCStatus.Error, `Invalid relays list`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n } else if (relays) {\n const relayList = relays.split(',').map(r => r.trim()).filter(Boolean);\n const invalidRelays = relayList.filter(relay => !isValidRelayUrl(relay));\n \n if (invalidRelays.length > 0) {\n const invalidRelaysList = invalidRelays.join(', ');\n this.conn.set(NCStatus.Error, `Invalid relay URLs: ${invalidRelaysList}. Relay URLs must start with 'wss://' or 'ws://'`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n }\n }\n\n this.errorMessage = \"\";\n return true;\n }\n\n protected async connectToNostr() {\n const seq = ++this.connectSeq;\n this.conn.set(NCStatus.Loading);\n try {\n await this.nostrService.connectToNostr(this.getRelays());\n if (seq !== this.connectSeq) return; // stale attempt\n this.conn.set(NCStatus.Ready);\n this.nostrReadyResolve?.();\n try {\n this.onNostrRelaysConnected();\n } catch (hookError) {\n console.error('Error in onNostrRelaysConnected hook:', hookError);\n }\n } catch (error) {\n if (seq !== this.connectSeq) return; // stale attempt\n console.error('Failed to connect to Nostr relays:', error);\n this.conn.set(NCStatus.Error, 'Failed to connect to relays');\n this.nostrReadyReject?.(error);\n }\n }\n\n protected ensureNostrConnected(): Promise<void> {\n return this.nostrReady;\n }\n\n protected getRelays() {\n return parseRelays(this.getAttribute('relays'));\n }\n\n protected getTheme() {\n this.theme = parseTheme(this.getAttribute('data-theme'));\n }\n\n /**\n * Delegate events within shadow DOM.\n * Example: this.delegateEvent('click', '#npub-copy', (e) => this.copyNpub(e));\n */\n protected delegateEvent<K extends keyof HTMLElementEventMap>(\n type: K,\n selector: string,\n handler: (event: HTMLElementEventMap[K]) => void\n ) {\n const root = this.shadowRoot;\n if (!root) return;\n\n // Attach once per (event type, selector)\n const key = `${type}:${selector}`;\n if ((this as any)._delegated?.has(key)) return;\n\n if (!(this as any)._delegated) (this as any)._delegated = new Set<string>();\n (this as any)._delegated.add(key);\n\n root.addEventListener(type, (e) => {\n const t = e.target as HTMLElement;\n if (t.closest(selector)) {\n handler(e as any);\n }\n });\n }\n\n protected addDelegatedListener<K extends keyof HTMLElementEventMap>(\n type: K,\n selector: string,\n handler: (event: HTMLElementEventMap[K]) => void\n ) {\n this.delegateEvent(type, selector, handler);\n }\n\n protected renderError(errorMessage: string): string {\n return `Error: ${errorMessage}`;\n }\n\n /**\n * Updates host element classes based on component status\n * This is a common pattern used by all components\n */\n protected updateHostClasses() {\n const isLoading = this.computeOverall() === NCStatus.Loading;\n const isError = this.computeOverall() === NCStatus.Error;\n const isReady = this.computeOverall() === NCStatus.Ready;\n \n // Remove all state classes\n this.classList.remove('is-clickable', 'is-disabled', 'is-error');\n \n // Add appropriate state class\n if (isLoading) {\n this.classList.add('is-disabled');\n } else if (isError) {\n this.classList.add('is-error');\n } else if (isReady) {\n this.classList.add('is-clickable');\n }\n }\n\n /**\n * Base render method that handles common render logic\n * Subclasses should override renderContent() instead of render()\n */\n protected render() {\n this.updateHostClasses();\n this.renderContent();\n }\n\n /**\n * Handles click events with njump.me default action\n * Creates custom event, dispatches it, and opens njump.me if not prevented\n */\n protected handleNjumpClick(\n eventType: string,\n detail: any,\n njumpPath: string\n ): void {\n if (this.computeOverall() !== NCStatus.Ready) return;\n \n const event = new CustomEvent(eventType, {\n detail,\n bubbles: true,\n composed: true,\n cancelable: true,\n });\n \n const notPrevented = this.dispatchEvent(event);\n if (notPrevented) {\n window.open(`https://njump.me/${njumpPath}`, '_blank', 'noopener,noreferrer');\n }\n }\n\n /**\n * Abstract method for component-specific rendering\n * Must be implemented by subclasses\n */\n protected abstract renderContent(): void;\n\n /** Private methods */\n private resetNostrReadyBarrier() {\n this.connectSeq++;\n this.nostrReady = new Promise<void>((resolve, reject) => {\n this.nostrReadyResolve = resolve;\n this.nostrReadyReject = reject;\n });\n }\n}\n","// SPDX-License-Identifier: MIT\n\n/**\n * Base Styles Utility for Nostr Components\n * =========================================\n * \n * This utility provides common base styles that can be shared across components.\n * It includes design tokens, common patterns, and utility functions.\n */\n\n/**\n * Generates minimal base styles for any Nostr component\n * Includes only essential design tokens and base component styles\n * Uses generic CSS variables that can be overridden by data-theme\n */\nexport function getBaseStyles(): string {\n return `\n :host {\n /* === GENERIC DESIGN TOKENS === */\n --nostrc-color-background: #ffffff;\n --nostrc-color-hover-background: rgba(0, 0, 0, 0.05);\n --nostrc-color-border: #e0e0e0;\n --nostrc-color-error-background: #ffebee;\n --nostrc-color-error-text: #d32f2f;\n --nostrc-color-error-icon: #d32f2f;\n \n /* === TYPOGRAPHY === */\n --nostrc-font-family-primary: ui-sans-serif, system-ui, sans-serif;\n --nostrc-font-family-mono: monospace;\n --nostrc-font-size-base: 1em;\n --nostrc-font-size-small: 0.8em;\n --nostrc-font-size-large: 1.2em;\n --nostrc-font-weight-normal: 400;\n --nostrc-font-weight-medium: 500;\n --nostrc-font-weight-bold: 700;\n \n /* === SPACING === */\n --nostrc-spacing-xs: 4px;\n --nostrc-spacing-sm: 8px;\n --nostrc-spacing-md: 12px;\n --nostrc-spacing-lg: 16px;\n --nostrc-spacing-xl: 20px;\n \n /* === BORDERS === */\n --nostrc-border-radius-sm: 4px;\n --nostrc-border-radius-md: 8px;\n --nostrc-border-radius-lg: 12px;\n --nostrc-border-radius-full: 50%;\n --nostrc-border-width: 1px;\n \n /* === SKELETON === */\n --nostrc-skeleton-color-min: #f0f0f0;\n --nostrc-skeleton-color-max: #e0e0e0;\n --nostrc-skeleton-duration: 1.5s;\n --nostrc-skeleton-timing-function: linear;\n --nostrc-skeleton-iteration-count: infinite;\n \n /* === TRANSITIONS === */\n --nostrc-transition-duration: 0.2s;\n --nostrc-transition-timing: ease;\n }\n \n :host(.is-disabled) {\n opacity: 0.7;\n cursor: not-allowed;\n }\n \n /* === ESSENTIAL UTILITY STYLES === */\n ${styleUtils.skeleton()}\n ${styleUtils.copyButton()}\n ${styleUtils.textRow()}\n ${styleUtils.profileName()}\n ${styleUtils.errorIcon()}\n `;\n}\n\n/**\n * Generates component-specific styles by combining base styles with custom styles\n * Uses CSS theme variables instead of theme prop\n */\nexport function getComponentStyles(customStyles: string): string {\n return `\n <style>\n ${getBaseStyles()}\n /* === COMPONENT-SPECIFIC STYLES === */\n ${customStyles}\n </style>\n `;\n}\n\nexport const styleUtils = {\n /**\n * Generates error state styles\n */\n error: () => `\n :host(.is-error) {\n color: var(--nostrc-color-error-text);\n }\n `,\n \n /**\n * Generates skeleton loading styles\n */\n skeleton: () => `\n .skeleton {\n background: linear-gradient(\n 90deg,\n var(--nostrc-skeleton-color-min) 0%,\n var(--nostrc-skeleton-color-max) 50%,\n var(--nostrc-skeleton-color-min) 100%\n );\n background-size: 200% 100%;\n animation: skeleton-loading var(--nostrc-skeleton-duration) var(--nostrc-skeleton-timing-function) var(--nostrc-skeleton-iteration-count);\n border-radius: var(--nostrc-border-radius-sm);\n height: 16px;\n margin-bottom: var(--nostrc-spacing-xs);\n }\n \n .skeleton:last-child {\n margin-bottom: 0;\n }\n \n @keyframes skeleton-loading {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .skeleton { animation: none; }\n }\n `,\n \n /**\n * Generates copy button styles\n */\n copyButton: () => `\n .nc-copy-btn {\n cursor: pointer;\n opacity: 0.7;\n transition: opacity var(--nostrc-transition-duration) var(--nostrc-transition-timing);\n font-size: 1.5em;\n border: none;\n background: transparent;\n color: var(--nostrc-color-text-muted);\n }\n \n .nc-copy-btn:hover {\n opacity: 1;\n }\n \n .nc-copy-btn.copied {\n color: var(--nostrc-color-accent);\n }\n `,\n \n /**\n * Generates profile name styles\n */\n profileName: () => `\n .nostr-profile-name {\n color: var(--nostrc-theme-text-primary, #333333);\n font-weight: var(--nostrc-font-weight-bold);\n padding-bottom: var(--nostrc-spacing-xs);\n }\n `,\n \n /**\n * Generates text row styles\n */\n textRow: () => `\n .text-row {\n display: flex;\n align-items: center;\n gap: var(--nostrc-spacing-sm);\n font-size: var(--nostrc-font-size-base);\n }\n \n .text-row.mono {\n font-family: var(--nostrc-font-family-mono);\n font-size: var(--nostrc-font-size-small);\n }\n `,\n \n /**\n * Generates error icon styles\n */\n errorIcon: () => `\n .error-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: var(--nostrc-border-radius-full);\n background-color: var(--nostrc-color-error-background);\n color: var(--nostrc-color-error-icon);\n font-size: 2em;\n margin: auto;\n }\n `,\n \n};\n"],"names":["decodeNpub","npub","decoded","nip19","error","hexToNpub","hex","isValidHex","maskNPub","npubString","length","npubLength","validateNpub","result","i","suffix","getPostStats","ndk","postId","reposts","NDKKind","isDirectRepost","repost","tag","isDirectReply","reply","repostsCount","likes","zapAmount","replies","replyCount","MILLISATS_PER_SAT","parseRelays","relaysAttr","list","r","isValidRelayUrl","DEFAULT_RELAYS","parseTheme","themeAttr","theme","parseBooleanAttribute","attr","escapeHtml","text","div","isValidUrl","url","parsed","u","type","validateNip05","nip05","validateBech32OfType","input","expected","validateNoteId","noteId","validateEventId","eventId","copyToClipboard","formatRelativeTime","ts","now","messageTime","diffMs","diffSec","mins","hours","days","months","years","NCStatus","EVT_STATUS","NostrBaseComponent","shadow","__publicField","NostrService","name","oldValue","newValue","key","next","prev","perKeyAttr","perKeyVal","overall","overallVal","_overall","vals","status","opts","e","relays","tagName","invalidRelays","relay","invalidRelaysList","seq","_a","hookError","_b","selector","handler","root","errorMessage","isLoading","isError","isReady","eventType","detail","njumpPath","event","resolve","reject","getBaseStyles","styleUtils","getComponentStyles","customStyles"],"mappings":"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"}
1
+ {"version":3,"file":"base-styles-Dmuzg8I4.js","sources":["../../src/common/utils.ts","../../src/base/base-component/nostr-base-component.ts","../../src/common/base-styles.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport NDK, { NDKKind, NDKEvent } from '@nostr-dev-kit/ndk';\nimport { nip19 } from \"nostr-tools\";\n\n\nimport { Theme } from './types';\nimport { DEFAULT_RELAYS, MILLISATS_PER_SAT } from './constants';\n\nexport const decodeNpub = (npub: string): string => {\n if (typeof npub !== 'string' || !npub.startsWith('npub1')) {\n return '';\n }\n\n try {\n const decoded = nip19.decode(npub);\n if (decoded && typeof decoded.data === 'string') {\n return decoded.data;\n }\n } catch (error) {\n console.error('Failed to decode npub:', error);\n }\n \n return '';\n};\n\n/**\n * Convert hex pubkey to npub\n */\nexport function hexToNpub(hex: string): string {\n if (!hex || !isValidHex(hex)) return '';\n try {\n return nip19.npubEncode(hex.toLowerCase());\n } catch (error) {\n console.error('Failed to encode hex to npub:', error);\n return '';\n }\n}\n\n// Could be npub, note1, naddr, nsec, etc.,\nexport const decodeNip19Entity = (entity: string): any => {\n if (typeof entity !== 'string' || !/^[a-z0-9]+1[ac-hj-np-z02-9]+/.test(entity)) {\n return null;\n }\n\n try {\n const decoded = nip19.decode(entity);\n return decoded?.data ?? null;\n } catch (error) {\n console.error('Failed to decode NIP-19 entity:', error);\n return null;\n }\n};\n\nexport function maskNPub(npubString: string = '', length = 3) {\n const npubLength = npubString.length;\n\n if (!npubString.startsWith('npub1')) {\n return 'Invalid nPub: expected npub1...';\n }\n\n if (!validateNpub(npubString)) {\n return 'Invalid nPub';\n }\n\n let result = 'npub1';\n\n for (let i = 5; i < length + 5; i++) {\n result += npubString[i];\n }\n\n result += '...';\n\n let suffix = '';\n for (let i = npubLength - 1; i >= npubLength - length; i--) {\n suffix = npubString[i] + suffix;\n }\n\n result += suffix;\n\n return result;\n}\n\nexport type Stats = {\n likes: number;\n reposts: number;\n zaps: number;\n replies: number;\n};\n\nexport async function getPostStats(ndk: NDK, postId: string): Promise<Stats> {\n const reposts = await ndk.fetchEvents({\n kinds: [NDKKind.Repost],\n '#e': [postId || ''],\n });\n\n const isDirectRepost = (repost: NDKEvent): boolean => {\n const pTagCounts = repost.tags.filter(tag => tag[0] === 'p').length;\n return pTagCounts === 1;\n };\n\n const isDirectReply = (reply: NDKEvent): boolean => {\n const eTagsCount = reply.tags.filter(tag => tag[0] === 'e').length;\n return eTagsCount === 1;\n };\n\n // Only take the count of direct reposts\n const repostsCount = Array.from(reposts).filter(isDirectRepost).length;\n\n const likes = await ndk.fetchEvents({\n kinds: [NDKKind.Reaction],\n '#e': [postId || ''],\n });\n\n // TODO: Add zap receipt validation - https://github.com/nostr-protocol/nips/blob/master/57.md#appendix-f-validating-zap-receipts\n // const zaps = await ndk.fetchEvents({\n // kinds: [NDKKind.Zap],\n // '#e': [postId || '']\n // });\n\n // const zapAmount = Array.from(zaps).reduce((prev, curr) => {\n // const bolt11Tag = curr.getMatchingTags('bolt11');\n\n // if(\n // !bolt11Tag ||\n // !Array.isArray(bolt11Tag) ||\n // bolt11Tag.length === 0 ||\n // !bolt11Tag[0] ||\n // !Array.isArray(bolt11Tag[0]) ||\n // (bolt11Tag[0] as string[]).length === 0\n // ) {\n // return prev;\n // }\n\n // const bolt11 = bolt11Tag[0][1];\n\n // const decodedbol11 = decode(bolt11);\n\n // const amountSection = decodedbol11.sections.find(section => section.name === 'amount');\n\n // if(amountSection) {\n // const millisats = Number(amountSection.value);\n\n // return prev + millisats;\n // }\n\n // return prev;\n // }, 0);\n\n const zapAmount = 0;\n\n const replies = await ndk.fetchEvents({\n kinds: [NDKKind.Text],\n '#e': [postId || ''],\n });\n\n // Only take the direct replies\n // https://github.com/nostr-protocol/nips/blob/master/10.md#positional-e-tags-deprecated\n const replyCount = Array.from(replies).filter(isDirectReply).length;\n\n return {\n likes: likes.size,\n reposts: repostsCount,\n zaps: zapAmount / MILLISATS_PER_SAT,\n replies: replyCount,\n };\n}\n\nexport function parseRelays(relaysAttr: string | null): string[] {\n if (relaysAttr) {\n const list = relaysAttr\n .split(',')\n .map(r => r.trim())\n .filter(Boolean)\n .filter(isValidRelayUrl);\n // fall back to defaults if user provided no valid entries\n return list.length ? Array.from(new Set(list)) : [...DEFAULT_RELAYS];\n }\n return [...DEFAULT_RELAYS];\n}\n\nexport function parseTheme(themeAttr: string | null): Theme {\n\n const theme = themeAttr?.trim().toLowerCase();\n\n if (theme === 'light' || theme === 'dark') {\n return theme;\n }\n\n return 'light';\n}\n\nexport function parseBooleanAttribute(attr: string | null): boolean {\n // Handles: \"true\", \"\", null, \"false\"\n if (attr === null) return false;\n if (attr === '' || attr.toLowerCase() === 'true') return true;\n return false;\n}\n\nexport function escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n\nexport function isValidUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return ['http:', 'https:'].includes(parsed.protocol);\n } catch {\n return false;\n }\n}\n\nexport function isValidRelayUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return u.protocol === 'wss:' || u.protocol === 'ws:';\n } catch {\n return false;\n }\n}\n\nexport function isValidHex(hex: string): boolean {\n return /^[0-9a-fA-F]+$/.test(hex) && hex.length === 64;\n}\n\nexport function validateNpub(npub: string): boolean {\n try {\n const { type } = nip19.decode(npub);\n return type === 'npub';\n } catch (e) {\n return false;\n }\n}\n\nexport function validateNip05(nip05: string): boolean {\n const nip05Regex = /^[a-zA-Z0-9_\\-\\.]+@[a-zA-Z0-9_\\-\\.]+\\.[a-zA-Z]{2,}$/;\n return nip05Regex.test(nip05);\n}\n\nfunction validateBech32OfType(input: string, expected: 'note' | 'nevent'): boolean {\n try {\n const { type } = nip19.decode(input);\n return type === expected;\n } catch {\n return false;\n }\n}\n\nexport function validateNoteId(noteId: string): boolean {\n return validateBech32OfType(noteId, 'note');\n}\n\nexport function validateEventId(eventId: string): boolean {\n return validateBech32OfType(eventId, 'nevent');\n}\n\nexport function copyToClipboard(text: string): Promise<void> {\n return navigator.clipboard.writeText(text)\n}\n\n/**\n * Format timestamp as relative time (e.g., \"2 mins ago\", \"1 month ago\")\n * @param ts Timestamp in seconds\n * @returns Formatted relative time string\n */\nexport function formatRelativeTime(ts: number): string {\n try {\n const now = Date.now();\n const messageTime = ts * 1000;\n const diffMs = now - messageTime;\n\n // Convert to seconds\n const diffSec = Math.floor(diffMs / 1000);\n\n if (diffSec < 60) {\n return 'just now';\n }\n\n // Minutes\n if (diffSec < 3600) {\n const mins = Math.floor(diffSec / 60);\n return `${mins} ${mins === 1 ? 'min' : 'mins'} ago`;\n }\n\n // Hours\n if (diffSec < 86400) {\n const hours = Math.floor(diffSec / 3600);\n return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;\n }\n\n // Days\n if (diffSec < 2592000) { // ~30 days\n const days = Math.floor(diffSec / 86400);\n return `${days} ${days === 1 ? 'day' : 'days'} ago`;\n }\n\n // Months\n if (diffSec < 31536000) { // ~365 days\n const months = Math.floor(diffSec / 2592000);\n return `${months} ${months === 1 ? 'month' : 'months'} ago`;\n }\n\n // Years\n const years = Math.floor(diffSec / 31536000);\n return `${years} ${years === 1 ? 'year' : 'years'} ago`;\n } catch (error) {\n console.error('Error formatting relative time:', error);\n return 'unknown';\n }\n}\n","// SPDX-License-Identifier: MIT\n\nimport { NostrService } from '../../common/nostr-service';\nimport { Theme } from '../../common/types';\nimport { parseRelays, parseTheme, isValidRelayUrl } from '../../common/utils';\n\nexport enum NCStatus {\n Idle, // 0\n Loading, // 1\n Ready, // 2\n Error, // 3\n}\n\nconst EVT_STATUS = 'nc:status';\n\n/**\n * NostrBaseComponent\n * ==================\n * Foundation for all Nostr web components in this library.\n *\n * Overview\n * - Manages relay connectivity via a shared `NostrService`.\n * - Parses common attributes (`theme`, `relays`) and applies theme.\n * - Provides a generic status management logic, that is extensible by derived classes.\n * - Offers small utilities useful to subclasses (event delegation, error snippet).\n *\n * Observed attributes\n * - `data-theme` — \"light\" | \"dark\"\n * - `relays` — CSV of relay URLs\n * \n * Events\n * - `nc:status` — from base, reflects connection and user/profile loading status\n * \n * TODO: Is this class doing too much work? Time to split into smaller components?\n */\nexport abstract class NostrBaseComponent extends HTMLElement {\n\n protected nostrService: NostrService = NostrService.getInstance();\n\n protected theme: Theme = 'light';\n protected errorMessage: string = '';\n\n protected nostrReady!: Promise<void>;\n protected nostrReadyResolve?: () => void;\n protected nostrReadyReject?: (e: unknown) => void;\n\n protected conn = this.channel('connection');\n\n private _statuses = new Map<string, NCStatus>();\n private _overall: NCStatus = NCStatus.Idle;\n\n // guard to ignore stale connects\n private connectSeq = 0;\n\n constructor(shadow: boolean = true) {\n super();\n if (shadow) this.attachShadow({ mode: 'open' });\n this.resetNostrReadyBarrier();\n }\n\n /** Lifecycle methods */\n static get observedAttributes() {\n return ['data-theme', 'relays'];\n }\n\n connectedCallback() {\n if (this.validateInputs()) {\n this.getTheme();\n // Avoid duplicate connects if a subclass handles it\n if (this.conn.get() === NCStatus.Idle) {\n void this.connectToNostr();\n }\n }\n }\n\n disconnectedCallback() {\n // Clean up delegated event listeners if shadow root exists\n if (this.shadowRoot && (this as any)._delegated) {\n (this as any)._delegated.clear();\n }\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null\n ) {\n if (oldValue === newValue) return;\n\n if (name === 'data-theme' || name === 'relays') {\n if (this.validateInputs()) {\n if (name === 'relays') {\n this.resetNostrReadyBarrier();\n void this.connectToNostr();\n }\n\n if (name === 'data-theme') {\n this.getTheme();\n this.render();\n }\n }\n }\n }\n\n /** Status map API */\n\n protected setStatusFor(key: string, next: NCStatus, error?: string) {\n const prev = this._statuses.get(key);\n const changed = prev !== next || (next === NCStatus.Error && !!error);\n\n if (!changed) return;\n\n this._statuses.set(key, next);\n\n if (next === NCStatus.Error && error) {\n this.errorMessage = error;\n } else if (prev === NCStatus.Error && next !== NCStatus.Error) {\n this.errorMessage = '';\n }\n\n // Reflect per-key attribute, e.g. user-status=\"loading\"\n const perKeyAttr = `${key}-status`;\n const perKeyVal = NCStatus[next].toLowerCase();\n if (this.getAttribute(perKeyAttr) !== perKeyVal) {\n this.setAttribute(perKeyAttr, perKeyVal);\n }\n\n // Compute & reflect overall for backward-compat CSS\n const overall = this.computeOverall();\n const overallVal = NCStatus[overall].toLowerCase();\n if (this._overall !== overall) {\n this._overall = overall;\n this.setAttribute('status', overallVal);\n this.onStatusChange(overall);\n } else if (overall === NCStatus.Error && error) {\n // propagate error updates even if overall state didn't flip\n this.onStatusChange(overall);\n }\n\n // Emit a single event with structured detail\n this.dispatchEvent(new CustomEvent(EVT_STATUS, {\n detail: {\n key,\n status: next,\n all: this.snapshotStatuses(),\n overall: this._overall,\n errorMessage: this.errorMessage || undefined,\n },\n bubbles: true,\n composed: true,\n }));\n }\n\n protected getStatusFor(key: string): NCStatus {\n return this._statuses.get(key) ?? NCStatus.Idle;\n }\n\n protected snapshotStatuses(): Record<string, NCStatus> {\n return Object.fromEntries(this._statuses.entries());\n }\n\n /** Overall status change hook */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n protected onStatusChange(_overall: NCStatus) { }\n\n /** Hook for subclasses to react when relays are connected */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n protected onNostrRelaysConnected() { }\n\n protected computeOverall(): NCStatus {\n const vals = [...this._statuses.values()];\n if (vals.includes(NCStatus.Error)) return NCStatus.Error;\n if (vals.includes(NCStatus.Loading)) return NCStatus.Loading;\n if (vals.includes(NCStatus.Ready)) return NCStatus.Ready;\n return NCStatus.Idle;\n }\n\n /**\n * Before first channel.set(), the map won't be set and hence get() will return `idle`.\n * In some cases (or maybe in all cases), this is not ideal.\n * In UserComponent, 'user' not set and hence returning `idle`, while\n * 'connect' switched between idle -> loading -> ready\n * and then\n * 'user' switches to loading -> ready,\n * results in loading -> ready -> loading -> ready in profile-badge onStatusChange.\n * \n * To avoid this, this function is called from UserComponent constructor,\n * to set default value as 'loading' without emitting onStatusChange event.\n * \n * Makes sense?\n * Try commenting this function call in UserComponent()\n * and add a log in ProfileBadge :: onStatusChange. You will get it.\n */\n protected initChannelStatus(key: string, status: NCStatus, opts = { reflectOverall: false }) {\n this._statuses.set(key, status);\n this.setAttribute(`${key}-status`, NCStatus[status].toLowerCase());\n if (opts.reflectOverall) {\n const overall = this.computeOverall();\n this._overall = overall;\n this.setAttribute('status', NCStatus[overall].toLowerCase());\n }\n }\n\n protected channel(key: string) {\n return {\n set: (s: NCStatus, e?: string) => this.setStatusFor(key, s, e),\n get: () => this.getStatusFor(key)\n };\n }\n\n /** Protected methods */\n protected validateInputs(): boolean {\n const theme = this.getAttribute('data-theme') || 'light';\n const relays = this.getAttribute('relays');\n const tagName = this.tagName.toLowerCase();\n\n if (!(theme === 'light' || theme === 'dark')) {\n this.conn.set(NCStatus.Error, `Invalid theme '${theme}'. Accepted values are 'light', 'dark'`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n } else if (relays && typeof relays != 'string') {\n this.conn.set(NCStatus.Error, `Invalid relays list`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n } else if (relays) {\n const relayList = relays.split(',').map(r => r.trim()).filter(Boolean);\n const invalidRelays = relayList.filter(relay => !isValidRelayUrl(relay));\n \n if (invalidRelays.length > 0) {\n const invalidRelaysList = invalidRelays.join(', ');\n this.conn.set(NCStatus.Error, `Invalid relay URLs: ${invalidRelaysList}. Relay URLs must start with 'wss://' or 'ws://'`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n }\n }\n\n this.errorMessage = \"\";\n return true;\n }\n\n protected async connectToNostr() {\n const seq = ++this.connectSeq;\n this.conn.set(NCStatus.Loading);\n try {\n await this.nostrService.connectToNostr(this.getRelays());\n if (seq !== this.connectSeq) return; // stale attempt\n this.conn.set(NCStatus.Ready);\n this.nostrReadyResolve?.();\n try {\n this.onNostrRelaysConnected();\n } catch (hookError) {\n console.error('Error in onNostrRelaysConnected hook:', hookError);\n }\n } catch (error) {\n if (seq !== this.connectSeq) return; // stale attempt\n console.error('Failed to connect to Nostr relays:', error);\n this.conn.set(NCStatus.Error, 'Failed to connect to relays');\n this.nostrReadyReject?.(error);\n }\n }\n\n protected ensureNostrConnected(): Promise<void> {\n return this.nostrReady;\n }\n\n protected getRelays() {\n return parseRelays(this.getAttribute('relays'));\n }\n\n protected getTheme() {\n this.theme = parseTheme(this.getAttribute('data-theme'));\n }\n\n /**\n * Delegate events within shadow DOM.\n * Example: this.delegateEvent('click', '#npub-copy', (e) => this.copyNpub(e));\n */\n protected delegateEvent<K extends keyof HTMLElementEventMap>(\n type: K,\n selector: string,\n handler: (event: HTMLElementEventMap[K]) => void\n ) {\n const root = this.shadowRoot;\n if (!root) return;\n\n // Attach once per (event type, selector)\n const key = `${type}:${selector}`;\n if ((this as any)._delegated?.has(key)) return;\n\n if (!(this as any)._delegated) (this as any)._delegated = new Set<string>();\n (this as any)._delegated.add(key);\n\n root.addEventListener(type, (e) => {\n const t = e.target as HTMLElement;\n if (t.closest(selector)) {\n handler(e as any);\n }\n });\n }\n\n protected addDelegatedListener<K extends keyof HTMLElementEventMap>(\n type: K,\n selector: string,\n handler: (event: HTMLElementEventMap[K]) => void\n ) {\n this.delegateEvent(type, selector, handler);\n }\n\n protected renderError(errorMessage: string): string {\n return `Error: ${errorMessage}`;\n }\n\n /**\n * Updates host element classes based on component status\n * This is a common pattern used by all components\n */\n protected updateHostClasses() {\n const isLoading = this.computeOverall() === NCStatus.Loading;\n const isError = this.computeOverall() === NCStatus.Error;\n const isReady = this.computeOverall() === NCStatus.Ready;\n \n // Remove all state classes\n this.classList.remove('is-clickable', 'is-disabled', 'is-error');\n \n // Add appropriate state class\n if (isLoading) {\n this.classList.add('is-disabled');\n } else if (isError) {\n this.classList.add('is-error');\n } else if (isReady) {\n this.classList.add('is-clickable');\n }\n }\n\n /**\n * Base render method that handles common render logic\n * Subclasses should override renderContent() instead of render()\n */\n protected render() {\n this.updateHostClasses();\n this.renderContent();\n }\n\n /**\n * Handles click events with njump.me default action\n * Creates custom event, dispatches it, and opens njump.me if not prevented\n */\n protected handleNjumpClick(\n eventType: string,\n detail: any,\n njumpPath: string\n ): void {\n if (this.computeOverall() !== NCStatus.Ready) return;\n \n const event = new CustomEvent(eventType, {\n detail,\n bubbles: true,\n composed: true,\n cancelable: true,\n });\n \n const notPrevented = this.dispatchEvent(event);\n if (notPrevented) {\n window.open(`https://njump.me/${njumpPath}`, '_blank', 'noopener,noreferrer');\n }\n }\n\n /**\n * Abstract method for component-specific rendering\n * Must be implemented by subclasses\n */\n protected abstract renderContent(): void;\n\n /** Private methods */\n private resetNostrReadyBarrier() {\n this.connectSeq++;\n this.nostrReady = new Promise<void>((resolve, reject) => {\n this.nostrReadyResolve = resolve;\n this.nostrReadyReject = reject;\n });\n }\n}\n","// SPDX-License-Identifier: MIT\n\n/**\n * Base Styles Utility for Nostr Components\n * =========================================\n * \n * This utility provides common base styles that can be shared across components.\n * It includes design tokens, common patterns, and utility functions.\n */\n\n/**\n * Generates minimal base styles for any Nostr component\n * Includes only essential design tokens and base component styles\n * Uses generic CSS variables that can be overridden by data-theme\n */\nexport function getBaseStyles(): string {\n return `\n :host {\n /* === GENERIC DESIGN TOKENS === */\n --nostrc-color-background: #ffffff;\n --nostrc-color-hover-background: rgba(0, 0, 0, 0.05);\n --nostrc-color-border: #e0e0e0;\n --nostrc-color-error-background: #ffebee;\n --nostrc-color-error-text: #d32f2f;\n --nostrc-color-error-icon: #d32f2f;\n \n /* === TYPOGRAPHY === */\n --nostrc-font-family-primary: ui-sans-serif, system-ui, sans-serif;\n --nostrc-font-family-mono: monospace;\n --nostrc-font-size-base: 1em;\n --nostrc-font-size-small: 0.8em;\n --nostrc-font-size-large: 1.2em;\n --nostrc-font-weight-normal: 400;\n --nostrc-font-weight-medium: 500;\n --nostrc-font-weight-bold: 700;\n \n /* === SPACING === */\n --nostrc-spacing-xs: 4px;\n --nostrc-spacing-sm: 8px;\n --nostrc-spacing-md: 12px;\n --nostrc-spacing-lg: 16px;\n --nostrc-spacing-xl: 20px;\n \n /* === BORDERS === */\n --nostrc-border-radius-sm: 4px;\n --nostrc-border-radius-md: 8px;\n --nostrc-border-radius-lg: 12px;\n --nostrc-border-radius-full: 50%;\n --nostrc-border-width: 1px;\n \n /* === SKELETON === */\n --nostrc-skeleton-color-min: #f0f0f0;\n --nostrc-skeleton-color-max: #e0e0e0;\n --nostrc-skeleton-duration: 1.5s;\n --nostrc-skeleton-timing-function: linear;\n --nostrc-skeleton-iteration-count: infinite;\n \n /* === TRANSITIONS === */\n --nostrc-transition-duration: 0.2s;\n --nostrc-transition-timing: ease;\n }\n \n :host(.is-disabled) {\n opacity: 0.7;\n cursor: not-allowed;\n }\n \n /* === ESSENTIAL UTILITY STYLES === */\n ${styleUtils.skeleton()}\n ${styleUtils.copyButton()}\n ${styleUtils.textRow()}\n ${styleUtils.profileName()}\n ${styleUtils.errorIcon()}\n `;\n}\n\n/**\n * Generates component-specific styles by combining base styles with custom styles\n * Uses CSS theme variables instead of theme prop\n */\nexport function getComponentStyles(customStyles: string): string {\n return `\n <style>\n ${getBaseStyles()}\n /* === COMPONENT-SPECIFIC STYLES === */\n ${customStyles}\n </style>\n `;\n}\n\nexport const styleUtils = {\n /**\n * Generates error state styles\n */\n error: () => `\n :host(.is-error) {\n color: var(--nostrc-color-error-text);\n }\n `,\n \n /**\n * Generates skeleton loading styles\n */\n skeleton: () => `\n .skeleton {\n background: linear-gradient(\n 90deg,\n var(--nostrc-skeleton-color-min) 0%,\n var(--nostrc-skeleton-color-max) 50%,\n var(--nostrc-skeleton-color-min) 100%\n );\n background-size: 200% 100%;\n animation: skeleton-loading var(--nostrc-skeleton-duration) var(--nostrc-skeleton-timing-function) var(--nostrc-skeleton-iteration-count);\n border-radius: var(--nostrc-border-radius-sm);\n height: 16px;\n margin-bottom: var(--nostrc-spacing-xs);\n }\n \n .skeleton:last-child {\n margin-bottom: 0;\n }\n \n @keyframes skeleton-loading {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .skeleton { animation: none; }\n }\n `,\n \n /**\n * Generates copy button styles\n */\n copyButton: () => `\n .nc-copy-btn {\n cursor: pointer;\n opacity: 0.7;\n transition: opacity var(--nostrc-transition-duration) var(--nostrc-transition-timing);\n font-size: 1.5em;\n border: none;\n background: transparent;\n color: var(--nostrc-color-text-muted);\n }\n \n .nc-copy-btn:hover {\n opacity: 1;\n }\n \n .nc-copy-btn.copied {\n color: var(--nostrc-color-accent);\n }\n `,\n \n /**\n * Generates profile name styles\n */\n profileName: () => `\n .nostr-profile-name {\n color: var(--nostrc-theme-text-primary, #333333);\n font-weight: var(--nostrc-font-weight-bold);\n padding-bottom: var(--nostrc-spacing-xs);\n }\n `,\n \n /**\n * Generates text row styles\n */\n textRow: () => `\n .text-row {\n display: flex;\n align-items: center;\n gap: var(--nostrc-spacing-sm);\n font-size: var(--nostrc-font-size-base);\n }\n \n .text-row.mono {\n font-family: var(--nostrc-font-family-mono);\n font-size: var(--nostrc-font-size-small);\n }\n `,\n \n /**\n * Generates error icon styles\n */\n errorIcon: () => `\n .error-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: var(--nostrc-border-radius-full);\n background-color: var(--nostrc-color-error-background);\n color: var(--nostrc-color-error-icon);\n font-size: 2em;\n margin: auto;\n }\n `,\n \n};\n"],"names":["decodeNpub","npub","decoded","nip19","error","hexToNpub","hex","isValidHex","maskNPub","npubString","length","npubLength","validateNpub","result","i","suffix","getPostStats","ndk","postId","reposts","NDKKind","isDirectRepost","repost","tag","isDirectReply","reply","repostsCount","likes","zapAmount","replies","replyCount","MILLISATS_PER_SAT","parseRelays","relaysAttr","list","r","isValidRelayUrl","DEFAULT_RELAYS","parseTheme","themeAttr","theme","parseBooleanAttribute","attr","escapeHtml","text","div","isValidUrl","url","parsed","u","type","validateNip05","nip05","validateBech32OfType","input","expected","validateNoteId","noteId","validateEventId","eventId","copyToClipboard","formatRelativeTime","ts","now","messageTime","diffMs","diffSec","mins","hours","days","months","years","NCStatus","EVT_STATUS","NostrBaseComponent","shadow","__publicField","NostrService","name","oldValue","newValue","key","next","prev","perKeyAttr","perKeyVal","overall","overallVal","_overall","vals","status","opts","e","relays","tagName","invalidRelays","relay","invalidRelaysList","seq","_a","hookError","_b","selector","handler","root","errorMessage","isLoading","isError","isReady","eventType","detail","njumpPath","event","resolve","reject","getBaseStyles","styleUtils","getComponentStyles","customStyles"],"mappings":"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"}
@@ -1,15 +1,15 @@
1
- import{e as r,m,d as f}from"./base-styles-DC0ilu4S.js";function l(e){const{value:a,display:t=a,className:s="",monospace:o=!1,title:n=t,showCopyButton:c=!1}=e,i=s.replace(/[^\w\- ]/g,""),p=r(t),u=r(n),d="&#x2398;";if(c){const y=r(a);return`
2
- <div class="${`text-row nc-copy ${o?"mono":""} ${i}`.trim()}" data-copy="${y}" title="${u}">
1
+ import{e as r,m,f}from"./base-styles-Dmuzg8I4.js";function l(e){const{value:a,display:t=a,className:s="",monospace:o=!1,title:n=t,showCopyButton:c=!1}=e,i=s.replace(/[^\w\- ]/g,""),p=r(t),u=r(n),y="&#x2398;";if(c){const d=r(a);return`
2
+ <div class="${`text-row nc-copy ${o?"mono":""} ${i}`.trim()}" data-copy="${d}" title="${u}">
3
3
  <span class="nc-copy-text">${p}</span>
4
4
  <button type="button"
5
5
  class="nc-copy-btn"
6
6
  aria-label="Copy"
7
7
  title="Copy"
8
- >${d}</button>
8
+ >${y}</button>
9
9
  </div>
10
10
  `}return`
11
11
  <div class="${`text-row ${o?"mono":""} ${i}`.trim()}" title="${u}">
12
12
  ${p}
13
13
  </div>
14
- `}function w(e){return l({value:e,display:m(e),monospace:!0,showCopyButton:!0,title:e})}function v(e){return l({value:e,display:e,monospace:!0,showCopyButton:!0})}function N(e){const{name:a,className:t="",title:s,showCopyButton:o=!1}=e;return l({value:a,display:a,className:`nostr-profile-name ${t}`.trim(),title:s??a,showCopyButton:o})}function g(e){e.addDelegatedListener("click",".nc-copy-btn",async a=>{var n;a.stopPropagation();const t=(n=a.target)==null?void 0:n.closest(".nc-copy-btn");if(!t)return;const s=t==null?void 0:t.closest(".nc-copy"),o=(s==null?void 0:s.dataset.copy)??"";if(o)try{await f(o),t.classList.add("copied");const c=t.getAttribute("aria-label")||"Copy";t.setAttribute("aria-label","Copied!"),setTimeout(()=>{t.classList.remove("copied"),t.setAttribute("aria-label",c)},1200)}catch{}})}export{v as a,w as b,g as c,l as d,N as r};
15
- //# sourceMappingURL=copy-delegation-CcagQMIW.js.map
14
+ `}function w(e){return l({value:e,display:m(e),monospace:!0,showCopyButton:!0,title:e})}function v(e){return l({value:e,display:e,monospace:!0,showCopyButton:!0})}function N(e){const{name:a,className:t="",title:s,showCopyButton:o=!1}=e;return l({value:a,display:a,className:`nostr-profile-name ${t}`.trim(),title:s??a,showCopyButton:o})}function g(e){e.addDelegatedListener("click",".nc-copy-btn",async a=>{var n;a.stopPropagation();const t=(n=a.target)==null?void 0:n.closest(".nc-copy-btn");if(!t)return;const s=t==null?void 0:t.closest(".nc-copy"),o=(s==null?void 0:s.dataset.copy)??"";if(o)try{await f(o),t.classList.add("copied");const c=t.getAttribute("aria-label")||"Copy";t.setAttribute("aria-label","Copied!"),setTimeout(()=>{t.classList.remove("copied"),t.setAttribute("aria-label",c)},1200)}catch{}})}export{v as a,w as b,l as c,g as d,N as r};
15
+ //# sourceMappingURL=copy-delegation-xzt-t_do.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"copy-delegation-CcagQMIW.js","sources":["../../src/base/text-row/render-text-row.ts","../../src/base/text-row/render-npub.ts","../../src/base/text-row/render-nip05.ts","../../src/base/text-row/render-name.ts","../../src/base/copy-delegation.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport { escapeHtml } from '../../common/utils';\n\nexport interface TextRowOptions {\n value: string; // the raw text to copy\n display?: string; // what to show (defaults to value)\n className?: string; // extra classes to add to the row\n monospace?: boolean; // show in mono font\n title?: string; // title/tooltip for the text\n showCopyButton?: boolean; // show copy button (default false)\n}\n\nexport function renderTextRow(opts: TextRowOptions): string {\n const {\n value,\n display = value,\n className = '',\n monospace = false,\n title = display,\n showCopyButton = false,\n } = opts;\n\n // Allow only class token chars to avoid breaking out of class attr\n const safeClassName = className.replace(/[^\\w\\- ]/g, '');\n const safeDisplay = escapeHtml(display);\n const safeTitle = escapeHtml(title);\n const iconHtml = '&#x2398;'; // ⎘\n\n if (showCopyButton) {\n const safeValue = escapeHtml(value);\n const rowClass = `text-row nc-copy ${monospace ? 'mono' : ''} ${safeClassName}`.trim();\n return `\n <div class=\"${rowClass}\" data-copy=\"${safeValue}\" title=\"${safeTitle}\">\n <span class=\"nc-copy-text\">${safeDisplay}</span>\n <button type=\"button\" \n class=\"nc-copy-btn\"\n aria-label=\"Copy\"\n title=\"Copy\"\n >${iconHtml}</button>\n </div>\n `;\n }\n\n const rowClass = `text-row ${monospace ? 'mono' : ''} ${safeClassName}`.trim();\n return `\n <div class=\"${rowClass}\" title=\"${safeTitle}\">\n ${safeDisplay}\n </div>\n `;\n}\n","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from \"./render-text-row\";\nimport { maskNPub } from \"../../common/utils\";\n\nexport function renderNpub(\n npub: string,\n): string {\n\n return renderTextRow({\n value: npub,\n display: maskNPub(npub),\n monospace: true,\n showCopyButton: true,\n title: npub,\n });\n}","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from \"./render-text-row\";\n\nexport function renderNip05(\n nip05: string,\n): string {\n return renderTextRow({\n value: nip05,\n display: nip05,\n monospace: true,\n showCopyButton: true,\n });\n}","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from './render-text-row';\n\nexport interface RenderNameOptions {\n name: string;\n className?: string;\n title?: string;\n showCopyButton?: boolean;\n}\n\nexport function renderName(options: RenderNameOptions): string {\n const { name, className = '', title, showCopyButton = false } = options;\n \n return renderTextRow({\n value: name,\n display: name,\n className: `nostr-profile-name ${className}`.trim(),\n title: title ?? name,\n showCopyButton: showCopyButton,\n });\n}\n","// SPDX-License-Identifier: MIT\n\nimport { copyToClipboard } from '../common/utils';\n\ninterface DelegatedHost {\n addDelegatedListener(\n type: string,\n selector: string,\n handler: (e: Event) => void\n ): void;\n }\n\nexport function attachCopyDelegation(host: DelegatedHost) {\n host.addDelegatedListener('click', '.nc-copy-btn', async (e: Event) => {\n e.stopPropagation();\n const btn = (e.target as HTMLElement)?.closest('.nc-copy-btn') as HTMLElement;\n if (!btn) return;\n const row = btn?.closest('.nc-copy') as HTMLElement | null;\n const value = row?.dataset.copy ?? '';\n if (!value) return;\n\n try {\n await copyToClipboard(value);\n btn.classList.add('copied');\n const prev = btn.getAttribute('aria-label') || 'Copy';\n btn.setAttribute('aria-label', 'Copied!');\n setTimeout(() => {\n btn.classList.remove('copied');\n btn.setAttribute('aria-label', prev);\n }, 1200);\n } catch {\n // optional: surface error // todo\n }\n });\n}\n"],"names":["renderTextRow","opts","value","display","className","monospace","title","showCopyButton","safeClassName","safeDisplay","escapeHtml","safeTitle","iconHtml","safeValue","renderNpub","npub","maskNPub","renderNip05","nip05","renderName","options","name","attachCopyDelegation","host","e","btn","_a","row","copyToClipboard","prev"],"mappings":"uDAaO,SAASA,EAAcC,EAA8B,CAC1D,KAAM,CACJ,MAAAC,EACA,QAAAC,EAAUD,EACV,UAAAE,EAAY,GACZ,UAAAC,EAAY,GACZ,MAAAC,EAAQH,EACR,eAAAI,EAAiB,EAAA,EACfN,EAGEO,EAAgBJ,EAAU,QAAQ,YAAa,EAAE,EACjDK,EAAcC,EAAWP,CAAO,EAChCQ,EAAcD,EAAWJ,CAAK,EAC9BM,EAAc,WAEpB,GAAIL,EAAgB,CAClB,MAAMM,EAAYH,EAAWR,CAAK,EAElC,MAAO;AAAA,oBADU,oBAAoBG,EAAY,OAAS,EAAE,IAAIG,CAAa,GAAG,KAAA,CAExD,gBAAgBK,CAAS,YAAYF,CAAS;AAAA,qCACrCF,CAAW;AAAA;AAAA;AAAA;AAAA;AAAA,iBAK/BG,CAAQ;AAAA;AAAA,KAGvB,CAGA,MAAO;AAAA,kBADU,YAAYP,EAAY,OAAS,EAAE,IAAIG,CAAa,GAAG,KAAA,CAEhD,YAAYG,CAAS;AAAA,QACvCF,CAAW;AAAA;AAAA,GAGnB,CC7CO,SAASK,EACdC,EACQ,CAER,OAAOf,EAAc,CACnB,MAAOe,EACP,QAASC,EAASD,CAAI,EACtB,UAAW,GACX,eAAgB,GAChB,MAAOA,CAAA,CACR,CACH,CCZO,SAASE,EACdC,EACQ,CACR,OAAOlB,EAAc,CACnB,MAAOkB,EACP,QAASA,EACT,UAAW,GACX,eAAgB,EAAA,CACjB,CACH,CCFO,SAASC,EAAWC,EAAoC,CAC7D,KAAM,CAAE,KAAAC,EAAM,UAAAjB,EAAY,GAAI,MAAAE,EAAO,eAAAC,EAAiB,IAAUa,EAEhE,OAAOpB,EAAc,CACnB,MAAOqB,EACP,QAASA,EACT,UAAW,sBAAsBjB,CAAS,GAAG,KAAA,EAC7C,MAAOE,GAASe,EAChB,eAAAd,CAAA,CACD,CACH,CCTO,SAASe,EAAqBC,EAAqB,CACxDA,EAAK,qBAAqB,QAAS,eAAgB,MAAOC,GAAa,OACrEA,EAAE,gBAAA,EACF,MAAMC,GAAOC,EAAAF,EAAE,SAAF,YAAAE,EAA0B,QAAQ,gBAC/C,GAAI,CAACD,EAAK,OACV,MAAME,EAAMF,GAAA,YAAAA,EAAK,QAAQ,YACnBvB,GAAQyB,GAAA,YAAAA,EAAK,QAAQ,OAAQ,GACnC,GAAKzB,EAEL,GAAI,CACF,MAAM0B,EAAgB1B,CAAK,EAC3BuB,EAAI,UAAU,IAAI,QAAQ,EAC1B,MAAMI,EAAOJ,EAAI,aAAa,YAAY,GAAK,OAC/CA,EAAI,aAAa,aAAc,SAAS,EACxC,WAAW,IAAM,CACfA,EAAI,UAAU,OAAO,QAAQ,EAC7BA,EAAI,aAAa,aAAcI,CAAI,CACrC,EAAG,IAAI,CACT,MAAQ,CAER,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"copy-delegation-xzt-t_do.js","sources":["../../src/base/text-row/render-text-row.ts","../../src/base/text-row/render-npub.ts","../../src/base/text-row/render-nip05.ts","../../src/base/text-row/render-name.ts","../../src/base/copy-delegation.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport { escapeHtml } from '../../common/utils';\n\nexport interface TextRowOptions {\n value: string; // the raw text to copy\n display?: string; // what to show (defaults to value)\n className?: string; // extra classes to add to the row\n monospace?: boolean; // show in mono font\n title?: string; // title/tooltip for the text\n showCopyButton?: boolean; // show copy button (default false)\n}\n\nexport function renderTextRow(opts: TextRowOptions): string {\n const {\n value,\n display = value,\n className = '',\n monospace = false,\n title = display,\n showCopyButton = false,\n } = opts;\n\n // Allow only class token chars to avoid breaking out of class attr\n const safeClassName = className.replace(/[^\\w\\- ]/g, '');\n const safeDisplay = escapeHtml(display);\n const safeTitle = escapeHtml(title);\n const iconHtml = '&#x2398;'; // ⎘\n\n if (showCopyButton) {\n const safeValue = escapeHtml(value);\n const rowClass = `text-row nc-copy ${monospace ? 'mono' : ''} ${safeClassName}`.trim();\n return `\n <div class=\"${rowClass}\" data-copy=\"${safeValue}\" title=\"${safeTitle}\">\n <span class=\"nc-copy-text\">${safeDisplay}</span>\n <button type=\"button\" \n class=\"nc-copy-btn\"\n aria-label=\"Copy\"\n title=\"Copy\"\n >${iconHtml}</button>\n </div>\n `;\n }\n\n const rowClass = `text-row ${monospace ? 'mono' : ''} ${safeClassName}`.trim();\n return `\n <div class=\"${rowClass}\" title=\"${safeTitle}\">\n ${safeDisplay}\n </div>\n `;\n}\n","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from \"./render-text-row\";\nimport { maskNPub } from \"../../common/utils\";\n\nexport function renderNpub(\n npub: string,\n): string {\n\n return renderTextRow({\n value: npub,\n display: maskNPub(npub),\n monospace: true,\n showCopyButton: true,\n title: npub,\n });\n}","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from \"./render-text-row\";\n\nexport function renderNip05(\n nip05: string,\n): string {\n return renderTextRow({\n value: nip05,\n display: nip05,\n monospace: true,\n showCopyButton: true,\n });\n}","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from './render-text-row';\n\nexport interface RenderNameOptions {\n name: string;\n className?: string;\n title?: string;\n showCopyButton?: boolean;\n}\n\nexport function renderName(options: RenderNameOptions): string {\n const { name, className = '', title, showCopyButton = false } = options;\n \n return renderTextRow({\n value: name,\n display: name,\n className: `nostr-profile-name ${className}`.trim(),\n title: title ?? name,\n showCopyButton: showCopyButton,\n });\n}\n","// SPDX-License-Identifier: MIT\n\nimport { copyToClipboard } from '../common/utils';\n\ninterface DelegatedHost {\n addDelegatedListener(\n type: string,\n selector: string,\n handler: (e: Event) => void\n ): void;\n }\n\nexport function attachCopyDelegation(host: DelegatedHost) {\n host.addDelegatedListener('click', '.nc-copy-btn', async (e: Event) => {\n e.stopPropagation();\n const btn = (e.target as HTMLElement)?.closest('.nc-copy-btn') as HTMLElement;\n if (!btn) return;\n const row = btn?.closest('.nc-copy') as HTMLElement | null;\n const value = row?.dataset.copy ?? '';\n if (!value) return;\n\n try {\n await copyToClipboard(value);\n btn.classList.add('copied');\n const prev = btn.getAttribute('aria-label') || 'Copy';\n btn.setAttribute('aria-label', 'Copied!');\n setTimeout(() => {\n btn.classList.remove('copied');\n btn.setAttribute('aria-label', prev);\n }, 1200);\n } catch {\n // optional: surface error // todo\n }\n });\n}\n"],"names":["renderTextRow","opts","value","display","className","monospace","title","showCopyButton","safeClassName","safeDisplay","escapeHtml","safeTitle","iconHtml","safeValue","renderNpub","npub","maskNPub","renderNip05","nip05","renderName","options","name","attachCopyDelegation","host","e","btn","_a","row","copyToClipboard","prev"],"mappings":"kDAaO,SAASA,EAAcC,EAA8B,CAC1D,KAAM,CACJ,MAAAC,EACA,QAAAC,EAAUD,EACV,UAAAE,EAAY,GACZ,UAAAC,EAAY,GACZ,MAAAC,EAAQH,EACR,eAAAI,EAAiB,EAAA,EACfN,EAGEO,EAAgBJ,EAAU,QAAQ,YAAa,EAAE,EACjDK,EAAcC,EAAWP,CAAO,EAChCQ,EAAcD,EAAWJ,CAAK,EAC9BM,EAAc,WAEpB,GAAIL,EAAgB,CAClB,MAAMM,EAAYH,EAAWR,CAAK,EAElC,MAAO;AAAA,oBADU,oBAAoBG,EAAY,OAAS,EAAE,IAAIG,CAAa,GAAG,KAAA,CAExD,gBAAgBK,CAAS,YAAYF,CAAS;AAAA,qCACrCF,CAAW;AAAA;AAAA;AAAA;AAAA;AAAA,iBAK/BG,CAAQ;AAAA;AAAA,KAGvB,CAGA,MAAO;AAAA,kBADU,YAAYP,EAAY,OAAS,EAAE,IAAIG,CAAa,GAAG,KAAA,CAEhD,YAAYG,CAAS;AAAA,QACvCF,CAAW;AAAA;AAAA,GAGnB,CC7CO,SAASK,EACdC,EACQ,CAER,OAAOf,EAAc,CACnB,MAAOe,EACP,QAASC,EAASD,CAAI,EACtB,UAAW,GACX,eAAgB,GAChB,MAAOA,CAAA,CACR,CACH,CCZO,SAASE,EACdC,EACQ,CACR,OAAOlB,EAAc,CACnB,MAAOkB,EACP,QAASA,EACT,UAAW,GACX,eAAgB,EAAA,CACjB,CACH,CCFO,SAASC,EAAWC,EAAoC,CAC7D,KAAM,CAAE,KAAAC,EAAM,UAAAjB,EAAY,GAAI,MAAAE,EAAO,eAAAC,EAAiB,IAAUa,EAEhE,OAAOpB,EAAc,CACnB,MAAOqB,EACP,QAASA,EACT,UAAW,sBAAsBjB,CAAS,GAAG,KAAA,EAC7C,MAAOE,GAASe,EAChB,eAAAd,CAAA,CACD,CACH,CCTO,SAASe,EAAqBC,EAAqB,CACxDA,EAAK,qBAAqB,QAAS,eAAgB,MAAOC,GAAa,OACrEA,EAAE,gBAAA,EACF,MAAMC,GAAOC,EAAAF,EAAE,SAAF,YAAAE,EAA0B,QAAQ,gBAC/C,GAAI,CAACD,EAAK,OACV,MAAME,EAAMF,GAAA,YAAAA,EAAK,QAAQ,YACnBvB,GAAQyB,GAAA,YAAAA,EAAK,QAAQ,OAAQ,GACnC,GAAKzB,EAEL,GAAI,CACF,MAAM0B,EAAgB1B,CAAK,EAC3BuB,EAAI,UAAU,IAAI,QAAQ,EAC1B,MAAMI,EAAOJ,EAAI,aAAa,YAAY,GAAK,OAC/CA,EAAI,aAAa,aAAc,SAAS,EACxC,WAAW,IAAM,CACfA,EAAI,UAAU,OAAO,QAAQ,EAC7BA,EAAI,aAAa,aAAcI,CAAI,CACrC,EAAG,IAAI,CACT,MAAQ,CAER,CACF,CAAC,CACH"}
Binary file
Binary file
@@ -8,8 +8,13 @@ var d=Object.defineProperty;var c=(a,t,e)=>t in a?d(a,t,{enumerable:!0,configura
8
8
  padding: var(--nostrc-spacing-xl, 20px);
9
9
  background: var(--nostrc-theme-bg, #ffffff);
10
10
  color: var(--nostrc-theme-text-primary, #000000);
11
- position: relative;
11
+ margin: auto;
12
12
  font-family: var(--nostrc-font-family-primary, ui-sans-serif, system-ui, sans-serif);
13
+ /* Ensure dialog is centered when opened */
14
+ position: fixed;
15
+ top: 50%;
16
+ left: 50%;
17
+ transform: translate(-50%, -50%);
13
18
  }
14
19
 
15
20
  .nostr-base-dialog[open] {
@@ -63,4 +68,4 @@ var d=Object.defineProperty;var c=(a,t,e)=>t in a?d(a,t,{enumerable:!0,configura
63
68
  color: var(--nostrc-theme-text-primary, #000000);
64
69
  }
65
70
  `;class g extends HTMLElement{constructor(){super();l(this,"dialog",null)}static get observedAttributes(){return["header","data-theme"]}injectStyles(){if(document.querySelector("style[data-dialog-component-styles]"))return;const e=document.createElement("style");e.setAttribute("data-dialog-component-styles","true"),e.textContent=h(),document.head.appendChild(e)}render(){this.injectStyles();const e=this.getAttribute("header")||"Dialog",o=this.getAttribute("data-theme");this.dialog=document.createElement("dialog"),this.dialog.className="nostr-base-dialog",o&&this.dialog.setAttribute("data-theme",o);const i=document.createElement("div");i.className="dialog-header";const s=document.createElement("h2");s.textContent=e;const n=document.createElement("button");n.className="dialog-close-btn",n.setAttribute("aria-label","Close dialog"),n.textContent="✕",i.appendChild(s),i.appendChild(n);const r=document.createElement("div");for(r.className="dialog-content";this.firstChild;)r.appendChild(this.firstChild);this.dialog.appendChild(i),this.dialog.appendChild(r),document.body.appendChild(this.dialog),this.setupEventListeners()}setupEventListeners(){if(!this.dialog)return;const e=this.dialog.querySelector(".dialog-close-btn");e==null||e.addEventListener("click",()=>{this.close()}),this.dialog.addEventListener("click",o=>{o.target===this.dialog&&this.close()}),this.dialog.addEventListener("cancel",o=>{o.preventDefault(),this.close()}),this.dialog.addEventListener("close",()=>{this.cleanup()})}show(){this.showModal()}showModal(){var e;this.dialog||this.render(),(e=this.dialog)==null||e.showModal()}close(){var e;(e=this.dialog)==null||e.close()}cleanup(){this.dialog&&this.dialog.isConnected&&this.dialog.remove(),this.isConnected&&this.remove(),this.dialog=null}disconnectedCallback(){this.cleanup()}attributeChangedCallback(e,o,i){if(e==="header"&&this.dialog){const s=this.dialog.querySelector(".dialog-header h2");s&&(s.textContent=i||"Dialog")}else e==="data-theme"&&this.dialog&&(i?this.dialog.setAttribute("data-theme",i):this.dialog.removeAttribute("data-theme"))}}customElements.get("dialog-component")||customElements.define("dialog-component",g);
66
- //# sourceMappingURL=dialog-component-Dqg0QU9I.js.map
71
+ //# sourceMappingURL=dialog-component-Da1ZIYh9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog-component-Da1ZIYh9.js","sources":["../../src/base/dialog-component/style.ts","../../src/base/dialog-component/dialog-component.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\n/**\n * Base dialog component styles\n * Provides common styling for all dialog components\n */\nexport const getDialogComponentStyles = (): string => {\n return `\n /* Base Dialog Styles */\n .nostr-base-dialog {\n width: 400px;\n max-width: 90vw;\n border: none;\n border-radius: var(--nostrc-border-radius-lg, 10px);\n padding: var(--nostrc-spacing-xl, 20px);\n background: var(--nostrc-theme-bg, #ffffff);\n color: var(--nostrc-theme-text-primary, #000000);\n margin: auto;\n font-family: var(--nostrc-font-family-primary, ui-sans-serif, system-ui, sans-serif);\n /* Ensure dialog is centered when opened */\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n }\n\n .nostr-base-dialog[open] {\n display: block;\n }\n\n .nostr-base-dialog::backdrop {\n background: rgba(0, 0, 0, 0.5);\n }\n\n .dialog-header {\n position: relative;\n margin-bottom: var(--nostrc-spacing-lg, 16px);\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .dialog-header h2 {\n font-size: var(--nostrc-font-size-large, 1.25rem);\n font-weight: var(--nostrc-font-weight-bold, 700);\n margin: 0;\n flex: 1;\n text-align: left;\n padding-top: 2px;\n color: var(--nostrc-theme-text-primary, #000000);\n }\n\n .dialog-close-btn {\n border: none;\n background: var(--nostrc-theme-hover-bg, #f7fafc);\n border-radius: var(--nostrc-border-radius-full, 50%);\n width: 32px;\n height: 32px;\n min-width: 32px;\n font-size: var(--nostrc-font-size-base, 16px);\n cursor: pointer;\n color: var(--nostrc-theme-text-secondary, #666666);\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .dialog-close-btn:hover {\n background: var(--nostrc-theme-border, rgba(0, 0, 0, 0.05));\n color: var(--nostrc-theme-text-primary, #000000);\n }\n\n .dialog-content {\n line-height: 1.6;\n color: var(--nostrc-theme-text-primary, #000000);\n }\n `;\n};\n\n","// SPDX-License-Identifier: MIT\n\nimport { getDialogComponentStyles } from './style';\n\n/**\n * Base dialog component that extends HTMLElement\n * Provides common dialog functionality with header, close button, and content area\n * \n * Usage:\n * ```typescript\n * const dialog = document.createElement('dialog-component');\n * dialog.setAttribute('header', 'Dialog Title');\n * dialog.innerHTML = '<p>Your content goes here</p>';\n * dialog.showModal(); // Don't append to body, just call showModal()\n * ```\n * \n * Features:\n * - Header with customizable text\n * - Close button\n * - Click outside to close\n * - ESC key to close\n * - Automatic cleanup on close\n * \n * Important: Only one instance of this component should be added to the DOM at any time.\n * Multiple instances may cause conflicts with event listeners and cleanup behavior.\n */\nexport class DialogComponent extends HTMLElement {\n private dialog: HTMLDialogElement | null = null;\n\n constructor() {\n super();\n }\n\n /**\n * Observed attributes for the component\n */\n static get observedAttributes() {\n return ['header', 'data-theme'];\n }\n\n /**\n * Inject dialog styles into document head\n * Prevents duplicate injection by checking for existing styles\n */\n private injectStyles(): void {\n if (document.querySelector('style[data-dialog-component-styles]')) return;\n \n const style = document.createElement('style');\n style.setAttribute('data-dialog-component-styles', 'true');\n style.textContent = getDialogComponentStyles();\n document.head.appendChild(style);\n }\n\n /**\n * Render the dialog\n */\n private render(): void {\n this.injectStyles();\n\n const headerText = this.getAttribute('header') || 'Dialog';\n const theme = this.getAttribute('data-theme');\n\n this.dialog = document.createElement('dialog');\n this.dialog.className = 'nostr-base-dialog';\n if (theme) {\n this.dialog.setAttribute('data-theme', theme);\n }\n\n const headerDiv = document.createElement('div');\n headerDiv.className = 'dialog-header';\n \n const headerH2 = document.createElement('h2');\n headerH2.textContent = headerText;\n \n const closeBtn = document.createElement('button');\n closeBtn.className = 'dialog-close-btn';\n closeBtn.setAttribute('aria-label', 'Close dialog');\n closeBtn.textContent = '✕';\n \n headerDiv.appendChild(headerH2);\n headerDiv.appendChild(closeBtn);\n\n const contentDiv = document.createElement('div');\n contentDiv.className = 'dialog-content';\n \n // Safely move child nodes from component to dialog content\n // This preserves any existing DOM nodes without using innerHTML\n while (this.firstChild) {\n contentDiv.appendChild(this.firstChild);\n }\n\n this.dialog.appendChild(headerDiv);\n this.dialog.appendChild(contentDiv);\n\n document.body.appendChild(this.dialog);\n\n this.setupEventListeners();\n }\n\n /**\n * Setup event listeners for closing the dialog\n */\n private setupEventListeners(): void {\n if (!this.dialog) return;\n\n // Close button click\n const closeBtn = this.dialog.querySelector('.dialog-close-btn');\n closeBtn?.addEventListener('click', () => {\n this.close();\n });\n\n // Click outside dialog (on backdrop)\n this.dialog.addEventListener('click', (e) => {\n if (e.target === this.dialog) {\n this.close();\n }\n });\n\n // ESC key handler\n this.dialog.addEventListener('cancel', (e) => {\n e.preventDefault();\n this.close();\n });\n\n // Cleanup on close\n this.dialog.addEventListener('close', () => {\n this.cleanup();\n });\n }\n\n /**\n * Show the dialog (alias for showModal)\n */\n public show(): void {\n this.showModal();\n }\n\n /**\n * Show the dialog as modal\n */\n public showModal(): void {\n if (!this.dialog) {\n this.render();\n }\n this.dialog?.showModal();\n }\n\n /**\n * Close the dialog\n */\n public close(): void {\n this.dialog?.close();\n }\n\n /**\n * Cleanup when dialog is closed\n */\n private cleanup(): void {\n if (this.dialog && this.dialog.isConnected) {\n this.dialog.remove();\n }\n if (this.isConnected) {\n this.remove();\n }\n this.dialog = null;\n }\n\n /**\n * Called when component is removed from DOM\n */\n disconnectedCallback(): void {\n this.cleanup();\n }\n\n /**\n * Called when observed attributes change\n */\n attributeChangedCallback(name: string, _oldValue: string, newValue: string): void {\n if (name === 'header' && this.dialog) {\n const heading = this.dialog.querySelector('.dialog-header h2');\n if (heading) {\n heading.textContent = newValue || 'Dialog';\n }\n } else if (name === 'data-theme' && this.dialog) {\n if (newValue) {\n this.dialog.setAttribute('data-theme', newValue);\n } else {\n this.dialog.removeAttribute('data-theme');\n }\n }\n }\n}\n\n// Define custom element\nif (!customElements.get('dialog-component')) {\n customElements.define('dialog-component', DialogComponent);\n}\n\n"],"names":["getDialogComponentStyles","DialogComponent","__publicField","style","headerText","theme","headerDiv","headerH2","closeBtn","contentDiv","e","_a","name","_oldValue","newValue","heading"],"mappings":"oKAMO,MAAMA,EAA2B,IAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,ICmBF,MAAMC,UAAwB,WAAY,CAG/C,aAAc,CACZ,MAAA,EAHMC,EAAA,cAAmC,KAI3C,CAKA,WAAW,oBAAqB,CAC9B,MAAO,CAAC,SAAU,YAAY,CAChC,CAMQ,cAAqB,CAC3B,GAAI,SAAS,cAAc,qCAAqC,EAAG,OAEnE,MAAMC,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,aAAa,+BAAgC,MAAM,EACzDA,EAAM,YAAcH,EAAA,EACpB,SAAS,KAAK,YAAYG,CAAK,CACjC,CAKQ,QAAe,CACrB,KAAK,aAAA,EAEL,MAAMC,EAAa,KAAK,aAAa,QAAQ,GAAK,SAC5CC,EAAQ,KAAK,aAAa,YAAY,EAE5C,KAAK,OAAS,SAAS,cAAc,QAAQ,EAC7C,KAAK,OAAO,UAAY,oBACpBA,GACF,KAAK,OAAO,aAAa,aAAcA,CAAK,EAG9C,MAAMC,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,UAAY,gBAEtB,MAAMC,EAAW,SAAS,cAAc,IAAI,EAC5CA,EAAS,YAAcH,EAEvB,MAAMI,EAAW,SAAS,cAAc,QAAQ,EAChDA,EAAS,UAAY,mBACrBA,EAAS,aAAa,aAAc,cAAc,EAClDA,EAAS,YAAc,IAEvBF,EAAU,YAAYC,CAAQ,EAC9BD,EAAU,YAAYE,CAAQ,EAE9B,MAAMC,EAAa,SAAS,cAAc,KAAK,EAK/C,IAJAA,EAAW,UAAY,iBAIhB,KAAK,YACVA,EAAW,YAAY,KAAK,UAAU,EAGxC,KAAK,OAAO,YAAYH,CAAS,EACjC,KAAK,OAAO,YAAYG,CAAU,EAElC,SAAS,KAAK,YAAY,KAAK,MAAM,EAErC,KAAK,oBAAA,CACP,CAKQ,qBAA4B,CAClC,GAAI,CAAC,KAAK,OAAQ,OAGlB,MAAMD,EAAW,KAAK,OAAO,cAAc,mBAAmB,EAC9DA,GAAA,MAAAA,EAAU,iBAAiB,QAAS,IAAM,CACxC,KAAK,MAAA,CACP,GAGA,KAAK,OAAO,iBAAiB,QAAUE,GAAM,CACvCA,EAAE,SAAW,KAAK,QACpB,KAAK,MAAA,CAET,CAAC,EAGD,KAAK,OAAO,iBAAiB,SAAWA,GAAM,CAC5CA,EAAE,eAAA,EACF,KAAK,MAAA,CACP,CAAC,EAGD,KAAK,OAAO,iBAAiB,QAAS,IAAM,CAC1C,KAAK,QAAA,CACP,CAAC,CACH,CAKO,MAAa,CAClB,KAAK,UAAA,CACP,CAKO,WAAkB,CDtIpB,IAAAC,ECuIE,KAAK,QACR,KAAK,OAAA,GAEPA,EAAA,KAAK,SAAL,MAAAA,EAAa,WACf,CAKO,OAAc,CDhJhB,IAAAA,GCiJHA,EAAA,KAAK,SAAL,MAAAA,EAAa,OACf,CAKQ,SAAgB,CAClB,KAAK,QAAU,KAAK,OAAO,aAC7B,KAAK,OAAO,OAAA,EAEV,KAAK,aACP,KAAK,OAAA,EAEP,KAAK,OAAS,IAChB,CAKA,sBAA6B,CAC3B,KAAK,QAAA,CACP,CAKA,yBAAyBC,EAAcC,EAAmBC,EAAwB,CAChF,GAAIF,IAAS,UAAY,KAAK,OAAQ,CACpC,MAAMG,EAAU,KAAK,OAAO,cAAc,mBAAmB,EACzDA,IACFA,EAAQ,YAAcD,GAAY,SAEtC,MAAWF,IAAS,cAAgB,KAAK,SACnCE,EACF,KAAK,OAAO,aAAa,aAAcA,CAAQ,EAE/C,KAAK,OAAO,gBAAgB,YAAY,EAG9C,CACF,CAGK,eAAe,IAAI,kBAAkB,GACxC,eAAe,OAAO,mBAAoBb,CAAe"}