@enbox/browser 0.3.40 → 0.3.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.mjs +110 -47
- package/dist/browser.mjs.map +3 -3
- package/dist/esm/browser-connect-handler.js +15 -5
- package/dist/esm/browser-connect-handler.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/ui/wallet-selector.js +391 -149
- package/dist/esm/ui/wallet-selector.js.map +1 -1
- package/dist/types/browser-connect-handler.d.ts +1 -1
- package/dist/types/browser-connect-handler.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/ui/wallet-selector.d.ts +33 -1
- package/dist/types/ui/wallet-selector.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/browser-connect-handler.ts +16 -6
- package/src/index.ts +2 -1
- package/src/ui/wallet-selector.ts +437 -162
|
@@ -4,15 +4,65 @@
|
|
|
4
4
|
* Injected into the DOM as a Shadow DOM element to prevent style conflicts.
|
|
5
5
|
* Inspired by WalletConnect's Web3Modal pattern.
|
|
6
6
|
*
|
|
7
|
+
* The modal is a single view with three zones:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Quick connect** — a one-tap button that connects with the recommended
|
|
10
|
+
* (first) wallet without scrolling the grid.
|
|
11
|
+
* 2. **Wallet grid** — a scrollable grid of wallet tiles with a search filter.
|
|
12
|
+
* Each tile renders the wallet's own favicon, falling back to a letter
|
|
13
|
+
* badge (never a third-party favicon proxy).
|
|
14
|
+
* 3. **Custom URL** — paste any wallet URL; it is validated against the
|
|
15
|
+
* wallet's `/.well-known/enbox-connect` discovery document before the
|
|
16
|
+
* selection resolves.
|
|
17
|
+
*
|
|
7
18
|
* @module
|
|
8
19
|
*/
|
|
20
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
21
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
22
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
23
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
24
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
25
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
26
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
/** Path of the wallet's connect discovery document, relative to its origin. */
|
|
30
|
+
export const WALLET_WELL_KNOWN_PATH = '/.well-known/enbox-connect';
|
|
31
|
+
/** Timeout applied to the well-known validation fetch. */
|
|
32
|
+
const WELL_KNOWN_FETCH_TIMEOUT_MS = 6000;
|
|
33
|
+
/**
|
|
34
|
+
* Fetch a wallet's `/.well-known/enbox-connect` discovery document to confirm
|
|
35
|
+
* the origin hosts an Enbox-compatible wallet.
|
|
36
|
+
*
|
|
37
|
+
* Resolves `true` only when the document is reachable and names a
|
|
38
|
+
* `connectServerUrl`. Unreachable origins, non-2xx responses, CORS failures,
|
|
39
|
+
* and malformed documents resolve `false`.
|
|
40
|
+
*/
|
|
41
|
+
export function probeWalletWellKnown(origin) {
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
try {
|
|
44
|
+
const wellKnownUrl = new URL(WALLET_WELL_KNOWN_PATH, origin).toString();
|
|
45
|
+
const response = yield fetch(wellKnownUrl, { signal: AbortSignal.timeout(WELL_KNOWN_FETCH_TIMEOUT_MS) });
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const payload = yield response.json();
|
|
50
|
+
return typeof (payload === null || payload === void 0 ? void 0 : payload.connectServerUrl) === 'string';
|
|
51
|
+
}
|
|
52
|
+
catch (_a) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
9
57
|
/** Shows the wallet selector modal and resolves with the chosen wallet URL. */
|
|
10
|
-
export function showWalletSelector(wallets) {
|
|
58
|
+
export function showWalletSelector(wallets, options = {}) {
|
|
59
|
+
var _a;
|
|
11
60
|
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
12
61
|
throw new Error('[@enbox/browser] Wallet selector is only available in browser environments.');
|
|
13
62
|
}
|
|
63
|
+
const validateWalletUrl = (_a = options.validateWalletUrl) !== null && _a !== void 0 ? _a : probeWalletWellKnown;
|
|
14
64
|
return new Promise((resolve, reject) => {
|
|
15
|
-
var _a, _b;
|
|
65
|
+
var _a, _b, _c;
|
|
16
66
|
// Create the host element with Shadow DOM isolation.
|
|
17
67
|
const host = document.createElement('div');
|
|
18
68
|
host.id = 'enbox-wallet-selector';
|
|
@@ -28,8 +78,16 @@ export function showWalletSelector(wallets) {
|
|
|
28
78
|
}
|
|
29
79
|
catch ( /* best effort */_a) { /* best effort */ }
|
|
30
80
|
};
|
|
81
|
+
const settleResolve = (url) => {
|
|
82
|
+
cleanup();
|
|
83
|
+
resolve(url);
|
|
84
|
+
};
|
|
85
|
+
const settleReject = () => {
|
|
86
|
+
cleanup();
|
|
87
|
+
reject(new Error('[@enbox/browser] Wallet selection cancelled.'));
|
|
88
|
+
};
|
|
31
89
|
// Detect dark mode.
|
|
32
|
-
const isDark = (_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, '(prefers-color-scheme: dark)').matches;
|
|
90
|
+
const isDark = (_b = (_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, '(prefers-color-scheme: dark)').matches) !== null && _b !== void 0 ? _b : false;
|
|
33
91
|
// Build styles.
|
|
34
92
|
const style = document.createElement('style');
|
|
35
93
|
style.textContent = buildStyles(isDark);
|
|
@@ -39,126 +97,248 @@ export function showWalletSelector(wallets) {
|
|
|
39
97
|
overlay.className = 'overlay';
|
|
40
98
|
const modal = document.createElement('div');
|
|
41
99
|
modal.className = 'modal';
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
title.textContent = 'Connect Wallet';
|
|
47
|
-
const closeBtn = document.createElement('button');
|
|
48
|
-
closeBtn.className = 'close-btn';
|
|
49
|
-
closeBtn.innerHTML = '×';
|
|
50
|
-
closeBtn.addEventListener('click', () => {
|
|
51
|
-
cleanup();
|
|
52
|
-
reject(new Error('[@enbox/browser] Wallet selection cancelled.'));
|
|
53
|
-
});
|
|
54
|
-
header.appendChild(title);
|
|
55
|
-
header.appendChild(closeBtn);
|
|
56
|
-
modal.appendChild(header);
|
|
57
|
-
// Wallet list
|
|
58
|
-
const list = document.createElement('div');
|
|
59
|
-
list.className = 'wallet-list';
|
|
60
|
-
for (const wallet of wallets) {
|
|
61
|
-
const item = document.createElement('button');
|
|
62
|
-
item.className = 'wallet-item';
|
|
63
|
-
const icon = document.createElement('img');
|
|
64
|
-
icon.src = (_b = wallet.icon) !== null && _b !== void 0 ? _b : faviconUrl(wallet.url);
|
|
65
|
-
icon.alt = wallet.name;
|
|
66
|
-
icon.width = 32;
|
|
67
|
-
icon.height = 32;
|
|
68
|
-
icon.onerror = () => { icon.style.display = 'none'; };
|
|
69
|
-
const textGroup = document.createElement('div');
|
|
70
|
-
textGroup.className = 'wallet-text';
|
|
71
|
-
const name = document.createElement('span');
|
|
72
|
-
name.className = 'wallet-name';
|
|
73
|
-
name.textContent = wallet.name;
|
|
74
|
-
textGroup.appendChild(name);
|
|
75
|
-
if (wallet.description) {
|
|
76
|
-
const desc = document.createElement('span');
|
|
77
|
-
desc.className = 'wallet-description';
|
|
78
|
-
desc.textContent = wallet.description;
|
|
79
|
-
textGroup.appendChild(desc);
|
|
80
|
-
}
|
|
81
|
-
const arrow = document.createElement('span');
|
|
82
|
-
arrow.className = 'arrow';
|
|
83
|
-
arrow.textContent = '\u203A'; // ›
|
|
84
|
-
item.appendChild(icon);
|
|
85
|
-
item.appendChild(textGroup);
|
|
86
|
-
item.appendChild(arrow);
|
|
87
|
-
item.addEventListener('click', () => {
|
|
88
|
-
cleanup();
|
|
89
|
-
resolve(wallet.url);
|
|
90
|
-
});
|
|
91
|
-
list.appendChild(item);
|
|
100
|
+
modal.appendChild(buildHeader(settleReject));
|
|
101
|
+
const recommended = wallets[0];
|
|
102
|
+
if (recommended !== undefined) {
|
|
103
|
+
modal.appendChild(buildQuickConnect(recommended, settleResolve));
|
|
92
104
|
}
|
|
93
|
-
modal.appendChild(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
sep.className = 'separator';
|
|
97
|
-
const sepLine1 = document.createElement('div');
|
|
98
|
-
sepLine1.className = 'sep-line';
|
|
99
|
-
const sepText = document.createElement('span');
|
|
100
|
-
sepText.textContent = 'or';
|
|
101
|
-
const sepLine2 = document.createElement('div');
|
|
102
|
-
sepLine2.className = 'sep-line';
|
|
103
|
-
sep.appendChild(sepLine1);
|
|
104
|
-
sep.appendChild(sepText);
|
|
105
|
-
sep.appendChild(sepLine2);
|
|
106
|
-
modal.appendChild(sep);
|
|
107
|
-
// Custom URL input
|
|
108
|
-
const inputGroup = document.createElement('div');
|
|
109
|
-
inputGroup.className = 'input-group';
|
|
110
|
-
const input = document.createElement('input');
|
|
111
|
-
input.type = 'url';
|
|
112
|
-
input.placeholder = 'Enter wallet URL...';
|
|
113
|
-
input.className = 'url-input';
|
|
114
|
-
const goBtn = document.createElement('button');
|
|
115
|
-
goBtn.className = 'go-btn';
|
|
116
|
-
goBtn.textContent = 'Connect';
|
|
117
|
-
goBtn.disabled = true;
|
|
118
|
-
input.addEventListener('input', () => {
|
|
119
|
-
goBtn.disabled = !isValidUrl(input.value);
|
|
120
|
-
});
|
|
121
|
-
input.addEventListener('keydown', (e) => {
|
|
122
|
-
if (e.key === 'Enter' && isValidUrl(input.value)) {
|
|
123
|
-
cleanup();
|
|
124
|
-
resolve(normalizeUrl(input.value));
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
goBtn.addEventListener('click', () => {
|
|
128
|
-
if (isValidUrl(input.value)) {
|
|
129
|
-
cleanup();
|
|
130
|
-
resolve(normalizeUrl(input.value));
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
inputGroup.appendChild(input);
|
|
134
|
-
inputGroup.appendChild(goBtn);
|
|
135
|
-
modal.appendChild(inputGroup);
|
|
105
|
+
modal.appendChild(buildWalletGrid(wallets, settleResolve));
|
|
106
|
+
modal.appendChild(buildSeparator());
|
|
107
|
+
modal.appendChild(buildCustomUrl(validateWalletUrl, settleResolve));
|
|
136
108
|
// Close on overlay click.
|
|
137
109
|
overlay.addEventListener('click', (e) => {
|
|
138
110
|
if (e.target === overlay) {
|
|
139
|
-
|
|
140
|
-
reject(new Error('[@enbox/browser] Wallet selection cancelled.'));
|
|
111
|
+
settleReject();
|
|
141
112
|
}
|
|
142
113
|
});
|
|
143
114
|
// Close on Escape.
|
|
144
115
|
onKeydown = (e) => {
|
|
145
116
|
if (e.key === 'Escape') {
|
|
146
|
-
|
|
147
|
-
reject(new Error('[@enbox/browser] Wallet selection cancelled.'));
|
|
117
|
+
settleReject();
|
|
148
118
|
}
|
|
149
119
|
};
|
|
150
120
|
document.addEventListener('keydown', onKeydown);
|
|
151
121
|
overlay.appendChild(modal);
|
|
152
122
|
shadow.appendChild(overlay);
|
|
153
123
|
document.body.appendChild(host);
|
|
154
|
-
// Focus the
|
|
155
|
-
|
|
124
|
+
// Focus the search box for keyboard-first users.
|
|
125
|
+
(_c = shadow.querySelector('.wallet-search')) === null || _c === void 0 ? void 0 : _c.focus();
|
|
156
126
|
});
|
|
157
127
|
}
|
|
158
|
-
// ───
|
|
159
|
-
function
|
|
160
|
-
|
|
128
|
+
// ─── DOM builders ────────────────────────────────────────────────
|
|
129
|
+
function buildHeader(onCancel) {
|
|
130
|
+
const header = document.createElement('div');
|
|
131
|
+
header.className = 'header';
|
|
132
|
+
const title = document.createElement('h2');
|
|
133
|
+
title.textContent = 'Connect a wallet';
|
|
134
|
+
const closeBtn = document.createElement('button');
|
|
135
|
+
closeBtn.className = 'close-btn';
|
|
136
|
+
closeBtn.setAttribute('aria-label', 'Close');
|
|
137
|
+
closeBtn.innerHTML = '×';
|
|
138
|
+
closeBtn.addEventListener('click', onCancel);
|
|
139
|
+
header.appendChild(title);
|
|
140
|
+
header.appendChild(closeBtn);
|
|
141
|
+
return header;
|
|
161
142
|
}
|
|
143
|
+
function buildQuickConnect(recommended, onSelect) {
|
|
144
|
+
const quick = document.createElement('button');
|
|
145
|
+
quick.className = 'quick-connect';
|
|
146
|
+
const bolt = document.createElement('span');
|
|
147
|
+
bolt.className = 'quick-bolt';
|
|
148
|
+
bolt.textContent = '⚡'; // ⚡
|
|
149
|
+
const text = document.createElement('span');
|
|
150
|
+
text.className = 'quick-text';
|
|
151
|
+
const label = document.createElement('span');
|
|
152
|
+
label.className = 'quick-label';
|
|
153
|
+
label.textContent = 'Quick connect';
|
|
154
|
+
const sub = document.createElement('span');
|
|
155
|
+
sub.className = 'quick-sub';
|
|
156
|
+
sub.textContent = `Connect with ${recommended.name}`;
|
|
157
|
+
text.appendChild(label);
|
|
158
|
+
text.appendChild(sub);
|
|
159
|
+
quick.appendChild(bolt);
|
|
160
|
+
quick.appendChild(text);
|
|
161
|
+
quick.addEventListener('click', () => onSelect(recommended.url));
|
|
162
|
+
return quick;
|
|
163
|
+
}
|
|
164
|
+
function buildWalletGrid(wallets, onSelect) {
|
|
165
|
+
const section = document.createElement('div');
|
|
166
|
+
section.className = 'grid-section';
|
|
167
|
+
const search = document.createElement('input');
|
|
168
|
+
search.type = 'search';
|
|
169
|
+
search.className = 'wallet-search';
|
|
170
|
+
search.placeholder = 'Search wallets…';
|
|
171
|
+
const grid = document.createElement('div');
|
|
172
|
+
grid.className = 'wallet-grid';
|
|
173
|
+
const empty = document.createElement('div');
|
|
174
|
+
empty.className = 'grid-empty';
|
|
175
|
+
empty.textContent = 'No wallets match your search.';
|
|
176
|
+
empty.style.display = 'none';
|
|
177
|
+
const tiles = wallets.map((wallet) => {
|
|
178
|
+
const tile = buildWalletTile(wallet, onSelect);
|
|
179
|
+
grid.appendChild(tile);
|
|
180
|
+
return { name: wallet.name.toLowerCase(), tile };
|
|
181
|
+
});
|
|
182
|
+
search.addEventListener('input', () => {
|
|
183
|
+
const query = search.value.trim().toLowerCase();
|
|
184
|
+
let visible = 0;
|
|
185
|
+
for (const { name, tile } of tiles) {
|
|
186
|
+
const match = name.includes(query);
|
|
187
|
+
tile.style.display = match ? '' : 'none';
|
|
188
|
+
if (match) {
|
|
189
|
+
visible += 1;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
empty.style.display = visible === 0 ? '' : 'none';
|
|
193
|
+
});
|
|
194
|
+
section.appendChild(search);
|
|
195
|
+
section.appendChild(grid);
|
|
196
|
+
section.appendChild(empty);
|
|
197
|
+
return section;
|
|
198
|
+
}
|
|
199
|
+
function buildWalletTile(wallet, onSelect) {
|
|
200
|
+
var _a;
|
|
201
|
+
const tile = document.createElement('button');
|
|
202
|
+
tile.className = 'wallet-item';
|
|
203
|
+
tile.title = (_a = wallet.description) !== null && _a !== void 0 ? _a : wallet.name;
|
|
204
|
+
tile.appendChild(buildWalletIcon(wallet));
|
|
205
|
+
const name = document.createElement('span');
|
|
206
|
+
name.className = 'wallet-name';
|
|
207
|
+
name.textContent = wallet.name;
|
|
208
|
+
tile.appendChild(name);
|
|
209
|
+
tile.addEventListener('click', () => onSelect(wallet.url));
|
|
210
|
+
return tile;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Build a wallet icon that prefers the wallet's own favicon and degrades to a
|
|
214
|
+
* letter badge. We never call a third-party favicon proxy: those 404 for many
|
|
215
|
+
* origins and leak the wallet URL to the proxy.
|
|
216
|
+
*/
|
|
217
|
+
function buildWalletIcon(wallet) {
|
|
218
|
+
const wrap = document.createElement('span');
|
|
219
|
+
wrap.className = 'wallet-icon';
|
|
220
|
+
const badge = document.createElement('span');
|
|
221
|
+
badge.className = 'wallet-badge';
|
|
222
|
+
badge.textContent = wallet.name.charAt(0).toUpperCase();
|
|
223
|
+
wrap.appendChild(badge);
|
|
224
|
+
const sources = iconCandidates(wallet);
|
|
225
|
+
if (sources.length === 0) {
|
|
226
|
+
return wrap;
|
|
227
|
+
}
|
|
228
|
+
const img = document.createElement('img');
|
|
229
|
+
img.className = 'wallet-img';
|
|
230
|
+
img.alt = '';
|
|
231
|
+
img.width = 40;
|
|
232
|
+
img.height = 40;
|
|
233
|
+
let index = 0;
|
|
234
|
+
img.addEventListener('error', () => {
|
|
235
|
+
index += 1;
|
|
236
|
+
if (index < sources.length) {
|
|
237
|
+
img.src = sources[index];
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
img.remove(); // fall back to the letter badge
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
img.src = sources[index];
|
|
244
|
+
wrap.appendChild(img);
|
|
245
|
+
return wrap;
|
|
246
|
+
}
|
|
247
|
+
/** Ordered favicon URLs to try for a wallet, most specific first. */
|
|
248
|
+
function iconCandidates(wallet) {
|
|
249
|
+
if (wallet.icon !== undefined) {
|
|
250
|
+
return [wallet.icon];
|
|
251
|
+
}
|
|
252
|
+
let origin;
|
|
253
|
+
try {
|
|
254
|
+
origin = new URL(wallet.url).origin;
|
|
255
|
+
}
|
|
256
|
+
catch (_a) {
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
return [`${origin}/favicon.svg`, `${origin}/favicon.ico`, `${origin}/favicon.png`];
|
|
260
|
+
}
|
|
261
|
+
function buildSeparator() {
|
|
262
|
+
const sep = document.createElement('div');
|
|
263
|
+
sep.className = 'separator';
|
|
264
|
+
const line1 = document.createElement('div');
|
|
265
|
+
line1.className = 'sep-line';
|
|
266
|
+
const sepText = document.createElement('span');
|
|
267
|
+
sepText.textContent = 'or';
|
|
268
|
+
const line2 = document.createElement('div');
|
|
269
|
+
line2.className = 'sep-line';
|
|
270
|
+
sep.appendChild(line1);
|
|
271
|
+
sep.appendChild(sepText);
|
|
272
|
+
sep.appendChild(line2);
|
|
273
|
+
return sep;
|
|
274
|
+
}
|
|
275
|
+
function buildCustomUrl(validateWalletUrl, onSelect) {
|
|
276
|
+
const section = document.createElement('div');
|
|
277
|
+
section.className = 'custom-section';
|
|
278
|
+
const label = document.createElement('label');
|
|
279
|
+
label.className = 'custom-label';
|
|
280
|
+
label.textContent = 'Have a wallet URL?';
|
|
281
|
+
const inputGroup = document.createElement('div');
|
|
282
|
+
inputGroup.className = 'input-group';
|
|
283
|
+
const input = document.createElement('input');
|
|
284
|
+
input.type = 'url';
|
|
285
|
+
input.placeholder = 'https://your-wallet.example';
|
|
286
|
+
input.className = 'url-input';
|
|
287
|
+
const goBtn = document.createElement('button');
|
|
288
|
+
goBtn.className = 'go-btn';
|
|
289
|
+
goBtn.textContent = 'Connect';
|
|
290
|
+
goBtn.disabled = true;
|
|
291
|
+
const errorLine = document.createElement('div');
|
|
292
|
+
errorLine.className = 'url-error';
|
|
293
|
+
errorLine.style.display = 'none';
|
|
294
|
+
// When validation fails we let the user override, since the popup handshake
|
|
295
|
+
// is the ultimate gate — a valid wallet behind strict CORS should not be
|
|
296
|
+
// permanently blocked.
|
|
297
|
+
let allowUnverified = false;
|
|
298
|
+
const resetState = () => {
|
|
299
|
+
allowUnverified = false;
|
|
300
|
+
goBtn.textContent = 'Connect';
|
|
301
|
+
errorLine.style.display = 'none';
|
|
302
|
+
goBtn.disabled = !isValidUrl(input.value);
|
|
303
|
+
};
|
|
304
|
+
const submit = () => __awaiter(this, void 0, void 0, function* () {
|
|
305
|
+
if (!isValidUrl(input.value)) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const origin = normalizeUrl(input.value);
|
|
309
|
+
if (allowUnverified) {
|
|
310
|
+
onSelect(origin);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
goBtn.disabled = true;
|
|
314
|
+
goBtn.textContent = 'Verifying…';
|
|
315
|
+
errorLine.style.display = 'none';
|
|
316
|
+
const valid = yield validateWalletUrl(origin);
|
|
317
|
+
if (valid) {
|
|
318
|
+
onSelect(origin);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
allowUnverified = true;
|
|
322
|
+
goBtn.disabled = false;
|
|
323
|
+
goBtn.textContent = 'Connect anyway';
|
|
324
|
+
errorLine.textContent = `Couldn't verify an Enbox wallet at ${origin}.`;
|
|
325
|
+
errorLine.style.display = '';
|
|
326
|
+
});
|
|
327
|
+
input.addEventListener('input', resetState);
|
|
328
|
+
input.addEventListener('keydown', (e) => {
|
|
329
|
+
if (e.key === 'Enter') {
|
|
330
|
+
void submit();
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
goBtn.addEventListener('click', () => void submit());
|
|
334
|
+
inputGroup.appendChild(input);
|
|
335
|
+
inputGroup.appendChild(goBtn);
|
|
336
|
+
section.appendChild(label);
|
|
337
|
+
section.appendChild(inputGroup);
|
|
338
|
+
section.appendChild(errorLine);
|
|
339
|
+
return section;
|
|
340
|
+
}
|
|
341
|
+
// ─── Helpers ─────────────────────────────────────────────────────
|
|
162
342
|
function isValidUrl(value) {
|
|
163
343
|
try {
|
|
164
344
|
const url = new URL(value.includes('://') ? value : `https://${value}`);
|
|
@@ -169,12 +349,9 @@ function isValidUrl(value) {
|
|
|
169
349
|
}
|
|
170
350
|
}
|
|
171
351
|
function normalizeUrl(value) {
|
|
172
|
-
|
|
173
|
-
value = `https://${value}`;
|
|
174
|
-
}
|
|
175
|
-
const url = new URL(value);
|
|
352
|
+
const withScheme = value.includes('://') ? value : `https://${value}`;
|
|
176
353
|
// Return origin only (strip path/trailing slash).
|
|
177
|
-
return
|
|
354
|
+
return new URL(withScheme).origin;
|
|
178
355
|
}
|
|
179
356
|
function buildStyles(isDark) {
|
|
180
357
|
const bg = isDark ? '#1a1a2e' : '#ffffff';
|
|
@@ -184,6 +361,8 @@ function buildStyles(isDark) {
|
|
|
184
361
|
const itemBg = isDark ? '#16213e' : '#f8f9fa';
|
|
185
362
|
const itemHover = isDark ? '#0f3460' : '#e9ecef';
|
|
186
363
|
const accent = isDark ? '#4a9eff' : '#0066cc';
|
|
364
|
+
const accentAlt = isDark ? '#7b5cff' : '#5b8def';
|
|
365
|
+
const danger = isDark ? '#ff6b6b' : '#c0392b';
|
|
187
366
|
const overlayBg = 'rgba(0, 0, 0, 0.5)';
|
|
188
367
|
return `
|
|
189
368
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -214,9 +393,9 @@ function buildStyles(isDark) {
|
|
|
214
393
|
background: ${bg};
|
|
215
394
|
color: ${text};
|
|
216
395
|
border-radius: 16px;
|
|
217
|
-
width:
|
|
218
|
-
max-width:
|
|
219
|
-
max-height:
|
|
396
|
+
width: 420px;
|
|
397
|
+
max-width: 92vw;
|
|
398
|
+
max-height: 86vh;
|
|
220
399
|
overflow-y: auto;
|
|
221
400
|
padding: 24px;
|
|
222
401
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
@@ -227,13 +406,10 @@ function buildStyles(isDark) {
|
|
|
227
406
|
display: flex;
|
|
228
407
|
justify-content: space-between;
|
|
229
408
|
align-items: center;
|
|
230
|
-
margin-bottom:
|
|
409
|
+
margin-bottom: 18px;
|
|
231
410
|
}
|
|
232
411
|
|
|
233
|
-
h2 {
|
|
234
|
-
font-size: 18px;
|
|
235
|
-
font-weight: 600;
|
|
236
|
-
}
|
|
412
|
+
h2 { font-size: 18px; font-weight: 600; }
|
|
237
413
|
|
|
238
414
|
.close-btn {
|
|
239
415
|
background: none;
|
|
@@ -247,51 +423,110 @@ function buildStyles(isDark) {
|
|
|
247
423
|
}
|
|
248
424
|
.close-btn:hover { color: ${text}; background: ${itemBg}; }
|
|
249
425
|
|
|
250
|
-
.
|
|
426
|
+
.quick-connect {
|
|
251
427
|
display: flex;
|
|
252
|
-
|
|
253
|
-
gap:
|
|
428
|
+
align-items: center;
|
|
429
|
+
gap: 12px;
|
|
430
|
+
width: 100%;
|
|
431
|
+
text-align: left;
|
|
432
|
+
padding: 14px 16px;
|
|
433
|
+
margin-bottom: 18px;
|
|
434
|
+
border: none;
|
|
435
|
+
border-radius: 12px;
|
|
436
|
+
cursor: pointer;
|
|
437
|
+
color: #fff;
|
|
438
|
+
background: linear-gradient(135deg, ${accent}, ${accentAlt});
|
|
439
|
+
transition: filter 0.15s;
|
|
440
|
+
}
|
|
441
|
+
.quick-connect:hover { filter: brightness(1.08); }
|
|
442
|
+
.quick-bolt { font-size: 22px; line-height: 1; }
|
|
443
|
+
.quick-text { display: flex; flex-direction: column; gap: 2px; }
|
|
444
|
+
.quick-label { font-weight: 600; font-size: 15px; }
|
|
445
|
+
.quick-sub { font-size: 12px; opacity: 0.85; }
|
|
446
|
+
|
|
447
|
+
.wallet-search {
|
|
448
|
+
width: 100%;
|
|
449
|
+
padding: 10px 14px;
|
|
450
|
+
margin-bottom: 12px;
|
|
451
|
+
border: 1px solid ${border};
|
|
452
|
+
border-radius: 10px;
|
|
453
|
+
font-size: 14px;
|
|
454
|
+
background: ${itemBg};
|
|
455
|
+
color: ${text};
|
|
456
|
+
outline: none;
|
|
457
|
+
}
|
|
458
|
+
.wallet-search:focus { border-color: ${accent}; }
|
|
459
|
+
.wallet-search::placeholder { color: ${muted}; }
|
|
460
|
+
|
|
461
|
+
.wallet-grid {
|
|
462
|
+
display: grid;
|
|
463
|
+
grid-template-columns: repeat(3, 1fr);
|
|
464
|
+
gap: 10px;
|
|
465
|
+
max-height: 216px;
|
|
466
|
+
overflow-y: auto;
|
|
467
|
+
padding: 2px;
|
|
254
468
|
}
|
|
255
469
|
|
|
256
470
|
.wallet-item {
|
|
257
471
|
display: flex;
|
|
472
|
+
flex-direction: column;
|
|
258
473
|
align-items: center;
|
|
259
|
-
|
|
260
|
-
|
|
474
|
+
justify-content: center;
|
|
475
|
+
gap: 8px;
|
|
476
|
+
padding: 14px 8px;
|
|
261
477
|
background: ${itemBg};
|
|
262
478
|
border: 1px solid ${border};
|
|
263
479
|
border-radius: 12px;
|
|
264
480
|
cursor: pointer;
|
|
265
|
-
font-size: 15px;
|
|
266
481
|
color: ${text};
|
|
267
|
-
transition: background 0.15s;
|
|
268
|
-
width: 100%;
|
|
269
|
-
text-align: left;
|
|
482
|
+
transition: background 0.15s, border-color 0.15s;
|
|
270
483
|
}
|
|
271
|
-
.wallet-item:hover { background: ${itemHover}; }
|
|
484
|
+
.wallet-item:hover { background: ${itemHover}; border-color: ${accent}; }
|
|
272
485
|
|
|
273
|
-
.wallet-
|
|
274
|
-
|
|
486
|
+
.wallet-icon {
|
|
487
|
+
position: relative;
|
|
488
|
+
width: 40px;
|
|
489
|
+
height: 40px;
|
|
275
490
|
display: flex;
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
491
|
+
align-items: center;
|
|
492
|
+
justify-content: center;
|
|
493
|
+
}
|
|
494
|
+
.wallet-badge {
|
|
495
|
+
position: absolute;
|
|
496
|
+
inset: 0;
|
|
497
|
+
display: flex;
|
|
498
|
+
align-items: center;
|
|
499
|
+
justify-content: center;
|
|
500
|
+
border-radius: 10px;
|
|
501
|
+
background: ${itemHover};
|
|
502
|
+
color: ${text};
|
|
503
|
+
font-size: 18px;
|
|
504
|
+
font-weight: 600;
|
|
505
|
+
}
|
|
506
|
+
.wallet-img {
|
|
507
|
+
position: relative;
|
|
508
|
+
width: 40px;
|
|
509
|
+
height: 40px;
|
|
510
|
+
border-radius: 10px;
|
|
511
|
+
object-fit: cover;
|
|
512
|
+
background: ${bg};
|
|
279
513
|
}
|
|
280
514
|
|
|
281
|
-
.wallet-name {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
color: ${muted};
|
|
286
|
-
white-space: nowrap;
|
|
515
|
+
.wallet-name {
|
|
516
|
+
font-size: 13px;
|
|
517
|
+
font-weight: 500;
|
|
518
|
+
max-width: 100%;
|
|
287
519
|
overflow: hidden;
|
|
288
520
|
text-overflow: ellipsis;
|
|
521
|
+
white-space: nowrap;
|
|
289
522
|
}
|
|
290
523
|
|
|
291
|
-
.
|
|
292
|
-
|
|
524
|
+
.grid-empty {
|
|
525
|
+
grid-column: 1 / -1;
|
|
526
|
+
padding: 16px;
|
|
527
|
+
text-align: center;
|
|
293
528
|
color: ${muted};
|
|
294
|
-
|
|
529
|
+
font-size: 13px;
|
|
295
530
|
}
|
|
296
531
|
|
|
297
532
|
.separator {
|
|
@@ -304,11 +539,15 @@ function buildStyles(isDark) {
|
|
|
304
539
|
}
|
|
305
540
|
.sep-line { flex: 1; height: 1px; background: ${border}; }
|
|
306
541
|
|
|
307
|
-
.
|
|
308
|
-
display:
|
|
309
|
-
|
|
542
|
+
.custom-label {
|
|
543
|
+
display: block;
|
|
544
|
+
font-size: 13px;
|
|
545
|
+
color: ${muted};
|
|
546
|
+
margin-bottom: 8px;
|
|
310
547
|
}
|
|
311
548
|
|
|
549
|
+
.input-group { display: flex; gap: 8px; }
|
|
550
|
+
|
|
312
551
|
.url-input {
|
|
313
552
|
flex: 1;
|
|
314
553
|
padding: 10px 14px;
|
|
@@ -333,11 +572,14 @@ function buildStyles(isDark) {
|
|
|
333
572
|
cursor: pointer;
|
|
334
573
|
white-space: nowrap;
|
|
335
574
|
}
|
|
336
|
-
.go-btn:disabled {
|
|
337
|
-
opacity: 0.4;
|
|
338
|
-
cursor: not-allowed;
|
|
339
|
-
}
|
|
575
|
+
.go-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
340
576
|
.go-btn:not(:disabled):hover { filter: brightness(1.1); }
|
|
577
|
+
|
|
578
|
+
.url-error {
|
|
579
|
+
margin-top: 8px;
|
|
580
|
+
font-size: 12px;
|
|
581
|
+
color: ${danger};
|
|
582
|
+
}
|
|
341
583
|
`;
|
|
342
584
|
}
|
|
343
585
|
//# sourceMappingURL=wallet-selector.js.map
|