@solid/react-component 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,918 @@
1
+ "use client";
2
+
3
+ // src/login/AuthGuard.tsx
4
+ import { useEffect, useRef, Suspense } from "react";
5
+ import { useSolidAuth } from "@ldo/solid-react";
6
+
7
+ // src/login/NavigationContext.tsx
8
+ import { createContext, useContext } from "react";
9
+ import { jsx } from "react/jsx-runtime";
10
+ var SolidLoginNavigationContext = createContext(null);
11
+ function SolidLoginNavigationProvider({
12
+ navigation,
13
+ config,
14
+ children
15
+ }) {
16
+ const fullConfig = {
17
+ loginPath: config?.loginPath ?? "/login",
18
+ homePath: config?.homePath ?? "/"
19
+ };
20
+ return /* @__PURE__ */ jsx(SolidLoginNavigationContext.Provider, { value: { navigation, config: fullConfig }, children });
21
+ }
22
+ function useSolidLoginNavigation() {
23
+ return useContext(SolidLoginNavigationContext);
24
+ }
25
+
26
+ // src/login/AuthGuard.tsx
27
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
28
+ var RETURN_TO_KEY = "solid-login-returnTo";
29
+ function storageGet(storage, key) {
30
+ try {
31
+ return storage.getItem(key);
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ function storageSet(storage, key, value) {
37
+ try {
38
+ storage.setItem(key, value);
39
+ } catch {
40
+ }
41
+ }
42
+ function storageRemove(storage, key) {
43
+ try {
44
+ storage.removeItem(key);
45
+ } catch {
46
+ }
47
+ }
48
+ function isValidReturnPath(value) {
49
+ return typeof value === "string" && value.startsWith("/") && !value.startsWith("//");
50
+ }
51
+ function resolveReturnTo(searchParams, loginPath, homePath) {
52
+ const fromParam = searchParams.get("returnTo");
53
+ if (isValidReturnPath(fromParam)) return fromParam;
54
+ if (typeof window === "undefined") return homePath;
55
+ const fromStorage = storageGet(sessionStorage, RETURN_TO_KEY);
56
+ if (isValidReturnPath(fromStorage)) return fromStorage;
57
+ const browserPath = window.location.pathname;
58
+ if (browserPath !== loginPath && browserPath !== homePath && isValidReturnPath(browserPath)) {
59
+ return browserPath;
60
+ }
61
+ return homePath;
62
+ }
63
+ var defaultFallback = /* @__PURE__ */ jsx2(
64
+ "div",
65
+ {
66
+ style: {
67
+ display: "flex",
68
+ minHeight: "100vh",
69
+ alignItems: "center",
70
+ justifyContent: "center",
71
+ background: "#fff"
72
+ },
73
+ children: /* @__PURE__ */ jsx2("span", { children: "Loading..." })
74
+ }
75
+ );
76
+ function AuthGuardContent({
77
+ children,
78
+ fallback = defaultFallback
79
+ }) {
80
+ const { session, ranInitialAuthCheck = true } = useSolidAuth();
81
+ const nav = useSolidLoginNavigation();
82
+ const pathname = nav?.navigation.getPathname() ?? "/";
83
+ const searchParams = nav?.navigation.getSearchParams() ?? { has: () => false, get: () => null };
84
+ const config = nav?.config ?? { loginPath: "/login", homePath: "/" };
85
+ const isOAuthCallback = searchParams.has("code") || searchParams.has("state");
86
+ const isLoginPage = pathname === config.loginPath;
87
+ const hasRedirectedRef = useRef(false);
88
+ const prevPathnameRef = useRef(pathname);
89
+ if (prevPathnameRef.current !== pathname) {
90
+ prevPathnameRef.current = pathname;
91
+ hasRedirectedRef.current = false;
92
+ }
93
+ useEffect(() => {
94
+ if (typeof window === "undefined" || !isLoginPage || session.isLoggedIn) return;
95
+ const returnTo = searchParams.get("returnTo");
96
+ if (isValidReturnPath(returnTo)) {
97
+ storageSet(sessionStorage, RETURN_TO_KEY, returnTo);
98
+ }
99
+ }, [isLoginPage, session.isLoggedIn]);
100
+ useEffect(() => {
101
+ if (!nav || !ranInitialAuthCheck || hasRedirectedRef.current) return;
102
+ const { navigation } = nav;
103
+ const sp = navigation.getSearchParams();
104
+ const path = navigation.getPathname();
105
+ const isCallback = sp.has("code") || sp.has("state");
106
+ const isLogin = path === config.loginPath;
107
+ if (isCallback && session.isLoggedIn) {
108
+ hasRedirectedRef.current = true;
109
+ let target = resolveReturnTo(sp, config.loginPath, config.homePath);
110
+ if (target === config.loginPath) target = config.homePath;
111
+ storageRemove(sessionStorage, RETURN_TO_KEY);
112
+ navigation.redirect(target);
113
+ return;
114
+ }
115
+ if (session.isLoggedIn && isLogin && !isCallback) {
116
+ hasRedirectedRef.current = true;
117
+ let target = resolveReturnTo(sp, config.loginPath, config.homePath);
118
+ if (target === config.loginPath) target = config.homePath;
119
+ storageRemove(sessionStorage, RETURN_TO_KEY);
120
+ navigation.replace(target);
121
+ return;
122
+ }
123
+ if (!session.isLoggedIn && !isLogin && !isCallback) {
124
+ hasRedirectedRef.current = true;
125
+ const current = path || "/";
126
+ if (current !== config.loginPath && current !== config.homePath) {
127
+ storageSet(sessionStorage, RETURN_TO_KEY, current);
128
+ }
129
+ const loginUrl = current === config.loginPath || current === config.homePath ? config.loginPath : `${config.loginPath}?returnTo=${encodeURIComponent(current)}`;
130
+ navigation.replace(loginUrl);
131
+ }
132
+ }, [ranInitialAuthCheck, session.isLoggedIn, pathname, nav, config.loginPath, config.homePath]);
133
+ if (!nav) {
134
+ if (process.env.NODE_ENV !== "production") {
135
+ console.warn(
136
+ "@solid/react-component: AuthGuard requires SolidLoginNavigationProvider (or use '@solid/react-component/login/next')"
137
+ );
138
+ }
139
+ return /* @__PURE__ */ jsx2(Fragment, { children });
140
+ }
141
+ if (!ranInitialAuthCheck) return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
142
+ if (isOAuthCallback) return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
143
+ if (!session.isLoggedIn && !isLoginPage) return null;
144
+ if (session.isLoggedIn && isLoginPage) return null;
145
+ return /* @__PURE__ */ jsx2(Fragment, { children });
146
+ }
147
+ function AuthGuard(props) {
148
+ return /* @__PURE__ */ jsx2(Suspense, { fallback: props.fallback ?? defaultFallback, children: /* @__PURE__ */ jsx2(AuthGuardContent, { ...props }) });
149
+ }
150
+
151
+ // src/login/SolidLoginPage.tsx
152
+ import {
153
+ useState as useState2,
154
+ useEffect as useEffect2,
155
+ useRef as useRef2,
156
+ useMemo,
157
+ useId
158
+ } from "react";
159
+
160
+ // src/login/useSolidLogin.ts
161
+ import { useState, useCallback } from "react";
162
+ import { useSolidAuth as useSolidAuth2 } from "@ldo/solid-react";
163
+ var DEFAULT_PRESETS = [
164
+ { label: "Solid Community", value: "https://solidcommunity.net/", secondaryLabel: "https://solidcommunity.net/" },
165
+ { label: "Inrupt", value: "https://login.inrupt.com", secondaryLabel: "https://login.inrupt.com" }
166
+ ];
167
+ function validateIssuerUrl(url) {
168
+ if (!url.trim()) return { valid: false, error: "Please enter a Solid Identity Provider URL" };
169
+ try {
170
+ const parsed = new URL(url);
171
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
172
+ return { valid: false, error: "URL must start with http:// or https://" };
173
+ }
174
+ } catch {
175
+ return { valid: false, error: "Please enter a valid URL" };
176
+ }
177
+ return { valid: true, error: null };
178
+ }
179
+ function useSolidLogin(options = {}) {
180
+ const { session, login } = useSolidAuth2();
181
+ const [issuerInput, setIssuerInput] = useState(options.defaultIssuer ?? "");
182
+ const [isLoading, setIsLoading] = useState(false);
183
+ const [error, setError] = useState(null);
184
+ const presetIssuers = options.presetIssuers ?? DEFAULT_PRESETS;
185
+ const validateAndSubmit = useCallback(async () => {
186
+ const trimmed = issuerInput.trim();
187
+ const { valid, error: err } = validateIssuerUrl(trimmed);
188
+ if (!valid) {
189
+ setError(err);
190
+ return false;
191
+ }
192
+ setError(null);
193
+ setIsLoading(true);
194
+ try {
195
+ await login(trimmed, options.redirectUrl ? { redirectUrl: options.redirectUrl } : void 0);
196
+ return true;
197
+ } catch (e) {
198
+ console.error("Login failed:", e);
199
+ setIsLoading(false);
200
+ return false;
201
+ }
202
+ }, [issuerInput, login, options.redirectUrl]);
203
+ const setIssuer = useCallback((value) => {
204
+ setIssuerInput(value);
205
+ setError(null);
206
+ }, []);
207
+ return {
208
+ session,
209
+ issuerInput,
210
+ setIssuerInput: setIssuer,
211
+ isLoading,
212
+ error,
213
+ presetIssuers,
214
+ validateAndSubmit,
215
+ login
216
+ };
217
+ }
218
+
219
+ // src/login/SolidLoginPage.tsx
220
+ import { Fragment as Fragment2, jsx as jsx3, jsxs } from "react/jsx-runtime";
221
+ var defaultTitle = "Sign in";
222
+ var defaultSubtitle = "to continue";
223
+ function ChevronDownIcon({
224
+ open,
225
+ style
226
+ }) {
227
+ return /* @__PURE__ */ jsx3(
228
+ "svg",
229
+ {
230
+ width: "20",
231
+ height: "20",
232
+ viewBox: "0 0 24 24",
233
+ fill: "none",
234
+ stroke: "currentColor",
235
+ strokeWidth: "2",
236
+ strokeLinecap: "round",
237
+ strokeLinejoin: "round",
238
+ style: {
239
+ transform: open ? "rotate(180deg)" : "rotate(0deg)",
240
+ transition: "transform 0.2s",
241
+ ...style
242
+ },
243
+ "aria-hidden": true,
244
+ children: /* @__PURE__ */ jsx3("path", { d: "m6 9 6 6 6-6" })
245
+ }
246
+ );
247
+ }
248
+ function GitHubIcon({ style }) {
249
+ return /* @__PURE__ */ jsx3(
250
+ "svg",
251
+ {
252
+ width: "16",
253
+ height: "16",
254
+ fill: "currentColor",
255
+ viewBox: "0 0 24 24",
256
+ "aria-hidden": true,
257
+ style,
258
+ children: /* @__PURE__ */ jsx3(
259
+ "path",
260
+ {
261
+ fillRule: "evenodd",
262
+ d: "M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z",
263
+ clipRule: "evenodd"
264
+ }
265
+ )
266
+ }
267
+ );
268
+ }
269
+ function ReportIssueIcon({ style }) {
270
+ return /* @__PURE__ */ jsx3(
271
+ "svg",
272
+ {
273
+ width: "16",
274
+ height: "16",
275
+ fill: "none",
276
+ stroke: "currentColor",
277
+ viewBox: "0 0 24 24",
278
+ "aria-hidden": true,
279
+ style,
280
+ children: /* @__PURE__ */ jsx3(
281
+ "path",
282
+ {
283
+ strokeLinecap: "round",
284
+ strokeLinejoin: "round",
285
+ strokeWidth: 2,
286
+ d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
287
+ }
288
+ )
289
+ }
290
+ );
291
+ }
292
+ function ButtonSpinner() {
293
+ return /* @__PURE__ */ jsx3(
294
+ "span",
295
+ {
296
+ style: {
297
+ display: "inline-block",
298
+ width: 14,
299
+ height: 14,
300
+ border: "2px solid transparent",
301
+ borderTopColor: "currentColor",
302
+ borderRadius: "50%",
303
+ animation: "solid-login-spin 0.6s linear infinite",
304
+ marginRight: 8,
305
+ verticalAlign: "middle"
306
+ }
307
+ }
308
+ );
309
+ }
310
+ function SolidLoginPage({
311
+ onAlreadyLoggedIn,
312
+ redirectUrl,
313
+ defaultIssuer,
314
+ presetIssuers,
315
+ logo,
316
+ logoAlt = "Logo",
317
+ title = defaultTitle,
318
+ subtitle = defaultSubtitle,
319
+ inputPlaceholder = "Enter your provider URL or select from the list",
320
+ inputLabel = "Solid Identity Provider",
321
+ buttonLabel = "Next",
322
+ buttonLoadingLabel = "Signing in...",
323
+ className = "",
324
+ footerGitHubUrl,
325
+ footerIssuesUrl,
326
+ renderLogo,
327
+ renderForm,
328
+ renderFooter
329
+ }) {
330
+ const {
331
+ session,
332
+ issuerInput,
333
+ setIssuerInput,
334
+ isLoading,
335
+ error,
336
+ presetIssuers: presets,
337
+ validateAndSubmit
338
+ } = useSolidLogin({ defaultIssuer, presetIssuers, redirectUrl });
339
+ const [showDropdown, setShowDropdown] = useState2(false);
340
+ const [highlightedIndex, setHighlightedIndex] = useState2(-1);
341
+ const inputRef = useRef2(null);
342
+ const dropdownRef = useRef2(null);
343
+ const generatedId = useId();
344
+ const inputId = `solid-login-combobox-${generatedId}`;
345
+ const listboxId = `${inputId}-listbox`;
346
+ useEffect2(() => {
347
+ if (session.isLoggedIn && onAlreadyLoggedIn) onAlreadyLoggedIn();
348
+ }, [session.isLoggedIn, onAlreadyLoggedIn]);
349
+ useEffect2(() => {
350
+ const handleClickOutside = (e) => {
351
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target) && inputRef.current && !inputRef.current.contains(e.target)) {
352
+ setShowDropdown(false);
353
+ }
354
+ };
355
+ if (showDropdown) {
356
+ document.addEventListener("mousedown", handleClickOutside);
357
+ return () => document.removeEventListener("mousedown", handleClickOutside);
358
+ }
359
+ }, [showDropdown]);
360
+ const filteredOptions = useMemo(() => {
361
+ if (!issuerInput.trim()) return presets;
362
+ const q = issuerInput.toLowerCase();
363
+ return presets.filter(
364
+ (o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q) || o.secondaryLabel && o.secondaryLabel.toLowerCase().includes(q)
365
+ );
366
+ }, [issuerInput, presets]);
367
+ if (session.isLoggedIn) return null;
368
+ const handleSubmit = (e) => {
369
+ e.preventDefault();
370
+ validateAndSubmit();
371
+ };
372
+ const handleSelect = (option) => {
373
+ setIssuerInput(option.value);
374
+ setShowDropdown(false);
375
+ setHighlightedIndex(-1);
376
+ inputRef.current?.focus();
377
+ };
378
+ const handleKeyDown = (e) => {
379
+ if (!showDropdown) {
380
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
381
+ e.preventDefault();
382
+ setShowDropdown(true);
383
+ setHighlightedIndex(-1);
384
+ }
385
+ return;
386
+ }
387
+ switch (e.key) {
388
+ case "ArrowDown":
389
+ e.preventDefault();
390
+ setHighlightedIndex(
391
+ (prev) => prev < filteredOptions.length - 1 ? prev + 1 : prev
392
+ );
393
+ break;
394
+ case "ArrowUp":
395
+ e.preventDefault();
396
+ setHighlightedIndex((prev) => prev > 0 ? prev - 1 : prev);
397
+ break;
398
+ case "Enter":
399
+ if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {
400
+ e.preventDefault();
401
+ handleSelect(filteredOptions[highlightedIndex]);
402
+ }
403
+ break;
404
+ case "Escape":
405
+ e.preventDefault();
406
+ setShowDropdown(false);
407
+ setHighlightedIndex(-1);
408
+ break;
409
+ }
410
+ };
411
+ const formProps = {
412
+ issuerInput,
413
+ setIssuerInput,
414
+ error,
415
+ presetIssuers: presets,
416
+ isLoading,
417
+ onSubmit: handleSubmit,
418
+ onIssuerChange: setIssuerInput
419
+ };
420
+ if (renderForm) {
421
+ return /* @__PURE__ */ jsx3(
422
+ "main",
423
+ {
424
+ className,
425
+ role: "main",
426
+ "aria-label": "Sign in page",
427
+ style: { display: "flex", minHeight: "100vh", background: "#fff" },
428
+ children: renderForm(formProps)
429
+ }
430
+ );
431
+ }
432
+ const showFooter = renderFooter || footerGitHubUrl != null && footerIssuesUrl != null;
433
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
434
+ /* @__PURE__ */ jsx3("style", { children: `
435
+ @keyframes solid-login-spin {
436
+ to { transform: rotate(360deg); }
437
+ }
438
+ .solid-login-combobox-input:focus {
439
+ border-color: #7B42F6;
440
+ box-shadow: 0 0 0 1px #7B42F6;
441
+ }
442
+ @media (max-width: 1023px) {
443
+ .solid-login-left-panel { display: none !important; }
444
+ .solid-login-mobile-header { display: flex !important; }
445
+ }
446
+ @media (min-width: 1024px) {
447
+ .solid-login-mobile-header { display: none !important; }
448
+ }
449
+ @media (min-width: 1024px) {
450
+ .solid-login-right-panel { min-width: 450px; }
451
+ }
452
+ ` }),
453
+ /* @__PURE__ */ jsxs(
454
+ "main",
455
+ {
456
+ className,
457
+ role: "main",
458
+ "aria-label": "Sign in page",
459
+ style: {
460
+ display: "flex",
461
+ flexDirection: "row",
462
+ flexWrap: "wrap",
463
+ minHeight: "100vh",
464
+ background: "#fff"
465
+ },
466
+ children: [
467
+ /* @__PURE__ */ jsx3(
468
+ "section",
469
+ {
470
+ className: "solid-login-left-panel",
471
+ "aria-label": "Branding section",
472
+ style: {
473
+ display: "flex",
474
+ flex: "1 1 50%",
475
+ minWidth: 280,
476
+ alignItems: "center",
477
+ justifyContent: "center",
478
+ borderRight: "1px solid #e5e7eb",
479
+ background: "#F3EDFF",
480
+ padding: "2rem"
481
+ },
482
+ children: /* @__PURE__ */ jsx3("div", { style: { maxWidth: "28rem", width: "100%" }, children: /* @__PURE__ */ jsxs(
483
+ "header",
484
+ {
485
+ style: {
486
+ display: "flex",
487
+ flexDirection: "column",
488
+ alignItems: "center",
489
+ justifyContent: "center",
490
+ gap: 4
491
+ },
492
+ children: [
493
+ renderLogo ? renderLogo() : logo ? /* @__PURE__ */ jsx3(
494
+ "div",
495
+ {
496
+ style: {
497
+ width: 300,
498
+ height: 90,
499
+ display: "flex",
500
+ alignItems: "center",
501
+ justifyContent: "center"
502
+ },
503
+ children: /* @__PURE__ */ jsx3(
504
+ "img",
505
+ {
506
+ src: logo,
507
+ alt: logoAlt,
508
+ style: { width: "100%", height: "100%", objectFit: "contain" }
509
+ }
510
+ )
511
+ }
512
+ ) : null,
513
+ /* @__PURE__ */ jsx3(
514
+ "h1",
515
+ {
516
+ style: {
517
+ marginBottom: 2,
518
+ fontSize: "2.25rem",
519
+ fontWeight: 400,
520
+ color: "#000"
521
+ },
522
+ children: title
523
+ }
524
+ ),
525
+ /* @__PURE__ */ jsx3("p", { style: { fontSize: "1rem", color: "#4b5563" }, children: subtitle })
526
+ ]
527
+ }
528
+ ) })
529
+ }
530
+ ),
531
+ /* @__PURE__ */ jsx3(
532
+ "section",
533
+ {
534
+ className: "solid-login-right-panel",
535
+ "aria-label": "Sign in form section",
536
+ style: {
537
+ display: "flex",
538
+ flex: "1 1 50%",
539
+ flexDirection: "column",
540
+ alignItems: "center",
541
+ justifyContent: "center",
542
+ background: "#fff",
543
+ padding: "3rem 1rem",
544
+ minWidth: 0,
545
+ minHeight: 320
546
+ },
547
+ children: /* @__PURE__ */ jsxs("div", { style: { width: "100%", maxWidth: "28rem" }, children: [
548
+ /* @__PURE__ */ jsxs(
549
+ "header",
550
+ {
551
+ className: "solid-login-mobile-header",
552
+ style: {
553
+ display: "none",
554
+ flexDirection: "column",
555
+ alignItems: "center",
556
+ justifyContent: "center",
557
+ marginBottom: "2rem"
558
+ },
559
+ children: [
560
+ renderLogo ? renderLogo() : logo ? /* @__PURE__ */ jsx3(
561
+ "div",
562
+ {
563
+ style: {
564
+ width: 300,
565
+ height: 90,
566
+ display: "flex",
567
+ alignItems: "center",
568
+ justifyContent: "center",
569
+ marginBottom: 8
570
+ },
571
+ children: /* @__PURE__ */ jsx3(
572
+ "img",
573
+ {
574
+ src: logo,
575
+ alt: logoAlt,
576
+ style: { width: "100%", height: "100%", objectFit: "contain" }
577
+ }
578
+ )
579
+ }
580
+ ) : null,
581
+ /* @__PURE__ */ jsx3(
582
+ "h1",
583
+ {
584
+ style: {
585
+ marginBottom: 8,
586
+ fontSize: "1.875rem",
587
+ fontWeight: 400,
588
+ color: "#000",
589
+ textAlign: "center"
590
+ },
591
+ children: title
592
+ }
593
+ ),
594
+ /* @__PURE__ */ jsx3("p", { style: { fontSize: "1rem", color: "#4b5563", textAlign: "center" }, children: subtitle })
595
+ ]
596
+ }
597
+ ),
598
+ /* @__PURE__ */ jsxs(
599
+ "form",
600
+ {
601
+ onSubmit: handleSubmit,
602
+ "aria-label": "Sign in form",
603
+ noValidate: true,
604
+ style: { display: "flex", flexDirection: "column", gap: "1.5rem" },
605
+ children: [
606
+ /* @__PURE__ */ jsx3(
607
+ "label",
608
+ {
609
+ htmlFor: inputId,
610
+ style: {
611
+ display: "block",
612
+ marginBottom: 8,
613
+ fontSize: "0.875rem",
614
+ fontWeight: 500,
615
+ color: "#000"
616
+ },
617
+ children: inputLabel
618
+ }
619
+ ),
620
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
621
+ /* @__PURE__ */ jsx3(
622
+ "input",
623
+ {
624
+ ref: inputRef,
625
+ id: inputId,
626
+ type: "text",
627
+ value: issuerInput,
628
+ onChange: (e) => setIssuerInput(e.target.value),
629
+ onFocus: () => {
630
+ setShowDropdown(true);
631
+ setHighlightedIndex(-1);
632
+ },
633
+ onKeyDown: handleKeyDown,
634
+ placeholder: inputPlaceholder,
635
+ disabled: isLoading,
636
+ "aria-invalid": !!error,
637
+ "aria-expanded": showDropdown,
638
+ "aria-controls": listboxId,
639
+ "aria-autocomplete": "list",
640
+ "aria-activedescendant": highlightedIndex >= 0 ? `${inputId}-option-${highlightedIndex}` : void 0,
641
+ role: "combobox",
642
+ autoComplete: "off",
643
+ className: "solid-login-combobox-input",
644
+ style: {
645
+ width: "100%",
646
+ height: 48,
647
+ paddingLeft: 16,
648
+ paddingRight: 40,
649
+ fontSize: "1rem",
650
+ color: "#000",
651
+ background: "#fff",
652
+ border: `1px solid ${error ? "#fca5a5" : "#d1d5db"}`,
653
+ borderRadius: 6,
654
+ outline: "none",
655
+ boxSizing: "border-box"
656
+ },
657
+ onBlur: () => {
658
+ }
659
+ }
660
+ ),
661
+ /* @__PURE__ */ jsx3(
662
+ "button",
663
+ {
664
+ type: "button",
665
+ onClick: () => {
666
+ setShowDropdown(!showDropdown);
667
+ inputRef.current?.focus();
668
+ },
669
+ "aria-label": showDropdown ? "Hide options" : "Show options",
670
+ "aria-expanded": showDropdown,
671
+ tabIndex: -1,
672
+ style: {
673
+ position: "absolute",
674
+ right: 12,
675
+ top: "50%",
676
+ transform: "translateY(-50%)",
677
+ padding: 0,
678
+ border: "none",
679
+ background: "none",
680
+ cursor: "pointer",
681
+ color: "#9ca3af"
682
+ },
683
+ children: /* @__PURE__ */ jsx3(ChevronDownIcon, { open: showDropdown })
684
+ }
685
+ ),
686
+ showDropdown && filteredOptions.length > 0 && /* @__PURE__ */ jsx3(
687
+ "div",
688
+ {
689
+ ref: dropdownRef,
690
+ id: listboxId,
691
+ role: "listbox",
692
+ "aria-label": "Options",
693
+ style: {
694
+ position: "absolute",
695
+ zIndex: 10,
696
+ marginTop: 4,
697
+ width: "100%",
698
+ maxHeight: 240,
699
+ overflow: "auto",
700
+ border: "1px solid #e5e7eb",
701
+ borderRadius: 6,
702
+ background: "#fff",
703
+ boxShadow: "0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05)"
704
+ },
705
+ children: filteredOptions.map((option, index) => /* @__PURE__ */ jsxs(
706
+ "button",
707
+ {
708
+ id: `${inputId}-option-${index}`,
709
+ type: "button",
710
+ role: "option",
711
+ "aria-selected": issuerInput === option.value,
712
+ onClick: () => handleSelect(option),
713
+ onMouseEnter: () => setHighlightedIndex(index),
714
+ style: {
715
+ width: "100%",
716
+ padding: "12px 16px",
717
+ textAlign: "left",
718
+ border: "none",
719
+ background: highlightedIndex === index ? "#f3f4f6" : "transparent",
720
+ cursor: "pointer",
721
+ fontSize: "0.875rem",
722
+ color: "#111"
723
+ },
724
+ children: [
725
+ /* @__PURE__ */ jsx3("div", { style: { fontWeight: 500, marginBottom: 2 }, children: option.label }),
726
+ option.secondaryLabel && /* @__PURE__ */ jsx3(
727
+ "div",
728
+ {
729
+ style: {
730
+ fontSize: "0.75rem",
731
+ color: "#6b7280",
732
+ overflow: "hidden",
733
+ textOverflow: "ellipsis",
734
+ whiteSpace: "nowrap"
735
+ },
736
+ children: option.secondaryLabel
737
+ }
738
+ )
739
+ ]
740
+ },
741
+ option.value
742
+ ))
743
+ }
744
+ )
745
+ ] }),
746
+ error && /* @__PURE__ */ jsx3(
747
+ "p",
748
+ {
749
+ role: "alert",
750
+ style: { fontSize: "0.75rem", color: "#dc2626", marginTop: 4 },
751
+ children: error
752
+ }
753
+ ),
754
+ /* @__PURE__ */ jsx3(
755
+ "div",
756
+ {
757
+ style: {
758
+ display: "flex",
759
+ justifyContent: "flex-end",
760
+ paddingTop: 16
761
+ },
762
+ children: /* @__PURE__ */ jsx3(
763
+ "button",
764
+ {
765
+ type: "submit",
766
+ disabled: isLoading,
767
+ "aria-busy": isLoading,
768
+ "aria-label": isLoading ? "Signing in, please wait" : "Continue to sign in",
769
+ style: {
770
+ padding: "8px 16px",
771
+ fontSize: "0.875rem",
772
+ fontWeight: 500,
773
+ color: "#fff",
774
+ background: isLoading ? "#d1d5db" : "#7B42F6",
775
+ border: "none",
776
+ borderRadius: 6,
777
+ cursor: isLoading ? "not-allowed" : "pointer",
778
+ display: "inline-flex",
779
+ alignItems: "center",
780
+ justifyContent: "center",
781
+ boxShadow: "none"
782
+ },
783
+ children: isLoading ? /* @__PURE__ */ jsxs(Fragment2, { children: [
784
+ /* @__PURE__ */ jsx3(ButtonSpinner, {}),
785
+ buttonLoadingLabel
786
+ ] }) : buttonLabel
787
+ }
788
+ )
789
+ }
790
+ )
791
+ ]
792
+ }
793
+ ),
794
+ showFooter && /* @__PURE__ */ jsx3(
795
+ "footer",
796
+ {
797
+ className: "solid-login-footer-wrap",
798
+ style: {
799
+ marginTop: "6rem",
800
+ display: "flex",
801
+ flexDirection: "column",
802
+ alignItems: "flex-end",
803
+ width: "100%"
804
+ },
805
+ children: renderFooter ? renderFooter() : /* @__PURE__ */ jsxs(
806
+ "div",
807
+ {
808
+ style: {
809
+ display: "flex",
810
+ alignItems: "center",
811
+ gap: 16,
812
+ fontSize: "0.875rem",
813
+ color: "#6b7280"
814
+ },
815
+ children: [
816
+ /* @__PURE__ */ jsxs(
817
+ "a",
818
+ {
819
+ href: footerGitHubUrl,
820
+ target: "_blank",
821
+ rel: "noopener noreferrer",
822
+ style: {
823
+ display: "inline-flex",
824
+ alignItems: "center",
825
+ gap: 6,
826
+ color: "inherit",
827
+ textDecoration: "none"
828
+ },
829
+ "aria-label": "View source code on GitHub",
830
+ children: [
831
+ /* @__PURE__ */ jsx3(GitHubIcon, {}),
832
+ /* @__PURE__ */ jsx3("span", { children: "GitHub" })
833
+ ]
834
+ }
835
+ ),
836
+ /* @__PURE__ */ jsx3("span", { style: { color: "#d1d5db" }, children: "\xB7" }),
837
+ /* @__PURE__ */ jsxs(
838
+ "a",
839
+ {
840
+ href: footerIssuesUrl,
841
+ target: "_blank",
842
+ rel: "noopener noreferrer",
843
+ style: {
844
+ display: "inline-flex",
845
+ alignItems: "center",
846
+ gap: 6,
847
+ color: "inherit",
848
+ textDecoration: "none"
849
+ },
850
+ "aria-label": "Report an issue on GitHub",
851
+ children: [
852
+ /* @__PURE__ */ jsx3(ReportIssueIcon, {}),
853
+ /* @__PURE__ */ jsx3("span", { children: "Report an issue" })
854
+ ]
855
+ }
856
+ )
857
+ ]
858
+ }
859
+ )
860
+ }
861
+ )
862
+ ] })
863
+ }
864
+ )
865
+ ]
866
+ }
867
+ )
868
+ ] });
869
+ }
870
+
871
+ // src/login/LoginFormControl.tsx
872
+ import { Fragment as Fragment3, jsx as jsx4 } from "react/jsx-runtime";
873
+ function LoginFormControl({
874
+ children,
875
+ defaultIssuer,
876
+ presetIssuers
877
+ }) {
878
+ const {
879
+ session,
880
+ issuerInput,
881
+ setIssuerInput,
882
+ isLoading,
883
+ error,
884
+ presetIssuers: presets,
885
+ validateAndSubmit
886
+ } = useSolidLogin({ defaultIssuer, presetIssuers });
887
+ if (session.isLoggedIn) return null;
888
+ const handleSubmit = (e) => {
889
+ e.preventDefault();
890
+ validateAndSubmit();
891
+ };
892
+ return /* @__PURE__ */ jsx4(Fragment3, { children: children({
893
+ issuerInput,
894
+ setIssuerInput,
895
+ isLoading,
896
+ error,
897
+ presetIssuers: presets,
898
+ onSubmit: handleSubmit,
899
+ onIssuerChange: setIssuerInput
900
+ }) });
901
+ }
902
+
903
+ // src/login/navigation.ts
904
+ var DEFAULT_CONFIG = {
905
+ loginPath: "/login",
906
+ homePath: "/"
907
+ };
908
+ export {
909
+ AuthGuard,
910
+ DEFAULT_CONFIG,
911
+ LoginFormControl,
912
+ SolidLoginNavigationProvider,
913
+ SolidLoginPage,
914
+ useSolidLogin,
915
+ useSolidLoginNavigation,
916
+ validateIssuerUrl
917
+ };
918
+ //# sourceMappingURL=index.js.map