@vertz/ui 0.2.15 → 0.2.16

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 (32) hide show
  1. package/README.md +49 -0
  2. package/dist/shared/chunk-14eqne2a.js +10 -0
  3. package/dist/shared/{chunk-dksg08fq.js → chunk-1rxa2fz4.js} +2 -8
  4. package/dist/shared/{chunk-8hsz5y4a.js → chunk-4fwcwxn6.js} +14 -4
  5. package/dist/shared/{chunk-hw67ckr3.js → chunk-4mtn7af6.js} +230 -19
  6. package/dist/shared/{chunk-4txc67nd.js → chunk-6jyt4ycw.js} +67 -2
  7. package/dist/shared/{chunk-83g4h38e.js → chunk-6wd36w21.js} +1 -0
  8. package/dist/shared/{chunk-h89w580h.js → chunk-afawz764.js} +1 -1
  9. package/dist/shared/{chunk-nn9v1zmk.js → chunk-b0qqqk03.js} +86 -21
  10. package/dist/shared/{chunk-1wby7nex.js → chunk-dhehvmj0.js} +161 -9
  11. package/dist/shared/{chunk-wymw818z.js → chunk-fkbgbf3n.js} +48 -9
  12. package/dist/shared/{chunk-5dbq8jp9.js → chunk-j09yyh34.js} +72 -6
  13. package/dist/shared/chunk-mtsvrj9e.js +23 -0
  14. package/dist/shared/chunk-pnv25zep.js +7 -0
  15. package/dist/shared/{chunk-j6qyxfdc.js → chunk-vndfjfdy.js} +3 -3
  16. package/dist/src/auth/public.d.ts +56 -9
  17. package/dist/src/auth/public.js +189 -13
  18. package/dist/src/css/public.d.ts +110 -2
  19. package/dist/src/css/public.js +8 -4
  20. package/dist/src/form/public.d.ts +29 -6
  21. package/dist/src/form/public.js +2 -2
  22. package/dist/src/index.d.ts +284 -13
  23. package/dist/src/index.js +161 -14
  24. package/dist/src/internals.d.ts +141 -5
  25. package/dist/src/internals.js +17 -9
  26. package/dist/src/query/public.js +4 -3
  27. package/dist/src/router/public.d.ts +17 -4
  28. package/dist/src/router/public.js +17 -11
  29. package/dist/src/test/index.d.ts +5 -0
  30. package/dist/src/test/index.js +4 -3
  31. package/package.json +3 -3
  32. package/reactivity.json +1 -11
@@ -114,6 +114,13 @@ declare const AccessContext: Context<AccessContextValue>;
114
114
  */
115
115
  declare function useAccessContext(): UnwrapSignals<AccessContextValue>;
116
116
  /**
117
+ * Entitlement registry — augmented by @vertz/codegen to narrow entitlement strings.
118
+ * When empty (no codegen), Entitlement falls back to `string`.
119
+ */
120
+ interface EntitlementRegistry {}
121
+ /** Entitlement type — narrows to literal union when codegen populates EntitlementRegistry. */
122
+ type Entitlement = keyof EntitlementRegistry extends never ? string : Extract<keyof EntitlementRegistry, string>;
123
+ /**
117
124
  * Check if the current user has a specific entitlement.
118
125
  *
119
126
  * Must be called in the component body (like query()/form()).
@@ -126,15 +133,9 @@ declare function useAccessContext(): UnwrapSignals<AccessContextValue>;
126
133
  * @param entitlement - The entitlement to check
127
134
  * @param entity - Optional entity with pre-computed `__access` metadata
128
135
  */
129
- declare function can(entitlement: string, entity?: {
136
+ declare function can(entitlement: Entitlement, entity?: {
130
137
  __access?: Record<string, AccessCheckData>;
131
138
  }): AccessCheck;
132
- /**
133
- * Access Event Client — WebSocket client for real-time access invalidation.
134
- *
135
- * Connects to the access event WebSocket endpoint, handles reconnection
136
- * with exponential backoff, and delivers parsed events to the caller.
137
- */
138
139
  /** Client-side access events (no orgId/userId — server scopes the broadcast) */
139
140
  type ClientAccessEvent = {
140
141
  type: "access:flag_toggled";
@@ -263,6 +264,15 @@ interface ResetInput {
263
264
  token: string;
264
265
  password: string;
265
266
  }
267
+ interface SignOutOptions {
268
+ /** Path to navigate to after sign-out completes. Uses SPA navigation (replace). */
269
+ redirectTo?: string;
270
+ }
271
+ interface OAuthProviderInfo {
272
+ id: string;
273
+ name: string;
274
+ authUrl: string;
275
+ }
266
276
  interface AuthResponse {
267
277
  user: User;
268
278
  expiresAt: number;
@@ -275,11 +285,12 @@ interface AuthContextValue {
275
285
  error: Signal<AuthClientError | null>;
276
286
  signIn: SdkMethodWithMeta<SignInInput, AuthResponse>;
277
287
  signUp: SdkMethodWithMeta<SignUpInput, AuthResponse>;
278
- signOut: () => Promise<void>;
288
+ signOut: (options?: SignOutOptions) => Promise<void>;
279
289
  refresh: () => Promise<void>;
280
290
  mfaChallenge: SdkMethodWithMeta<MfaInput, AuthResponse>;
281
291
  forgotPassword: SdkMethodWithMeta<ForgotInput, void>;
282
292
  resetPassword: SdkMethodWithMeta<ResetInput, void>;
293
+ providers: Signal<OAuthProviderInfo[]>;
283
294
  }
284
295
  declare const AuthContext: Context<AuthContextValue>;
285
296
  declare function useAuth(): UnwrapSignals<AuthContextValue>;
@@ -313,4 +324,40 @@ declare function AuthGate({ fallback, children }: AuthGateProps): ReadonlySignal
313
324
  * ```
314
325
  */
315
326
  declare function createAccessProvider(): AccessContextValue;
316
- export { useAuth, useAccessContext, createAccessProvider, createAccessEventClient, can, User, SignUpInput, SignInInput, ResetInput, MfaInput, ForgotInput, DenialReason, DenialMeta, ClientAccessEvent, AuthStatus, AuthResponse, AuthProviderProps, AuthProvider, AuthGateProps, AuthGate, AuthErrorCode, AuthContextValue, AuthContext, AuthClientError, AccessSet, AccessGateProps, AccessGate, AccessEventClientOptions, AccessEventClient, AccessContextValue, AccessContext, AccessCheckData, AccessCheck };
327
+ interface OAuthButtonProps {
328
+ /** Provider ID (e.g., 'github', 'google') */
329
+ provider: string;
330
+ /** Custom label text. Defaults to "Continue with {Name}". */
331
+ label?: string;
332
+ /** Render icon only, no text. */
333
+ iconOnly?: boolean;
334
+ /** @internal — injected providers array for testing. Uses useAuth() in production. */
335
+ _providers?: OAuthProviderInfo[];
336
+ }
337
+ declare function OAuthButton({ provider, label, iconOnly, _providers }: OAuthButtonProps): Element;
338
+ interface OAuthButtonsProps {
339
+ /** @internal — injected providers array for testing. Uses useAuth() in production. */
340
+ _providers?: OAuthProviderInfo[];
341
+ }
342
+ declare function OAuthButtons({ _providers }?: OAuthButtonsProps): HTMLDivElement;
343
+ interface ProtectedRouteProps {
344
+ /** Path to redirect to when unauthenticated. Default: '/login' */
345
+ loginPath?: string;
346
+ /** Rendered while auth is resolving (idle/loading). Default: null */
347
+ fallback?: () => unknown;
348
+ /** Rendered when authenticated */
349
+ children: (() => unknown) | unknown;
350
+ /** Optional: required entitlements (integrates with can()) */
351
+ requires?: Entitlement[];
352
+ /** Rendered when authenticated but lacking required entitlements. Default: null */
353
+ forbidden?: () => unknown;
354
+ /** Append ?returnTo=<currentPath> when redirecting. Default: true */
355
+ returnTo?: boolean;
356
+ }
357
+ declare function ProtectedRoute({ loginPath, fallback, children, requires, forbidden, returnTo }: ProtectedRouteProps): ReadonlySignal<unknown> | unknown;
358
+ /**
359
+ * Get an SVG icon string for a provider.
360
+ * Returns the built-in icon for known providers, or a generic fallback for unknown.
361
+ */
362
+ declare function getProviderIcon(providerId: string, size: number): string;
363
+ export { useAuth, useAccessContext, getProviderIcon, createAccessProvider, createAccessEventClient, can, User, SignUpInput, SignOutOptions, SignInInput, ResetInput, ProtectedRouteProps, ProtectedRoute, OAuthProviderInfo, OAuthButtonsProps, OAuthButtons, OAuthButtonProps, OAuthButton, MfaInput, ForgotInput, EntitlementRegistry, Entitlement, DenialReason, DenialMeta, ClientAccessEvent, AuthStatus, AuthResponse, AuthProviderProps, AuthProvider, AuthGateProps, AuthGate, AuthErrorCode, AuthContextValue, AuthContext, AuthClientError, AccessSet, AccessGateProps, AccessGate, AccessEventClientOptions, AccessEventClient, AccessContextValue, AccessContext, AccessCheckData, AccessCheck };
@@ -1,10 +1,29 @@
1
+ import {
2
+ RouterContext
3
+ } from "../../shared/chunk-mtsvrj9e.js";
4
+ import {
5
+ __on
6
+ } from "../../shared/chunk-pnv25zep.js";
7
+ import {
8
+ isBrowser
9
+ } from "../../shared/chunk-14eqne2a.js";
10
+ import {
11
+ __append,
12
+ __element,
13
+ __enterChildren,
14
+ __exitChildren,
15
+ __staticText
16
+ } from "../../shared/chunk-vndfjfdy.js";
17
+ import"../../shared/chunk-prj7nm08.js";
18
+ import"../../shared/chunk-afawz764.js";
1
19
  import {
2
20
  _tryOnCleanup,
3
21
  computed,
4
22
  createContext,
23
+ domEffect,
5
24
  signal,
6
25
  useContext
7
- } from "../../shared/chunk-8hsz5y4a.js";
26
+ } from "../../shared/chunk-4fwcwxn6.js";
8
27
 
9
28
  // src/auth/access-context.ts
10
29
  var AccessContext = createContext(undefined, "@vertz/ui::AccessContext");
@@ -69,7 +88,7 @@ function createAccessEventClient(options) {
69
88
  function getUrl() {
70
89
  if (options.url)
71
90
  return options.url;
72
- if (typeof window !== "undefined") {
91
+ if (isBrowser()) {
73
92
  const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
74
93
  return `${protocol}//${window.location.host}/api/auth/access-events`;
75
94
  }
@@ -454,7 +473,7 @@ function createTokenRefresh({ onRefresh }) {
454
473
  pendingOfflineRefresh = false;
455
474
  }
456
475
  let visibilityHandler;
457
- if (typeof document !== "undefined") {
476
+ if (isBrowser()) {
458
477
  visibilityHandler = () => {
459
478
  if (document.visibilityState === "hidden") {
460
479
  clearTimer();
@@ -465,7 +484,7 @@ function createTokenRefresh({ onRefresh }) {
465
484
  document.addEventListener("visibilitychange", visibilityHandler);
466
485
  }
467
486
  let onlineHandler;
468
- if (typeof window !== "undefined") {
487
+ if (isBrowser()) {
469
488
  onlineHandler = () => {
470
489
  if (pendingOfflineRefresh) {
471
490
  pendingOfflineRefresh = false;
@@ -476,10 +495,10 @@ function createTokenRefresh({ onRefresh }) {
476
495
  }
477
496
  function dispose() {
478
497
  cancel();
479
- if (visibilityHandler && typeof document !== "undefined") {
498
+ if (visibilityHandler && isBrowser()) {
480
499
  document.removeEventListener("visibilitychange", visibilityHandler);
481
500
  }
482
- if (onlineHandler && typeof window !== "undefined") {
501
+ if (onlineHandler && isBrowser()) {
483
502
  window.removeEventListener("online", onlineHandler);
484
503
  }
485
504
  }
@@ -502,9 +521,19 @@ function AuthProvider({
502
521
  flagEntitlementMap,
503
522
  children
504
523
  }) {
524
+ const router = useContext(RouterContext);
505
525
  const userSignal = signal(null);
506
526
  const statusSignal = signal("idle");
507
527
  const errorSignal = signal(null);
528
+ const providersSignal = signal([]);
529
+ if (isBrowser()) {
530
+ setTimeout(() => {
531
+ fetch(`${basePath}/providers`).then((res) => res.ok ? res.json() : []).then((data) => {
532
+ providersSignal.value = data;
533
+ }).catch(() => {});
534
+ }, 0);
535
+ }
536
+ let deferredRefreshTimer = null;
508
537
  const isAuthenticated = computed(() => statusSignal.value === "authenticated");
509
538
  const isLoading = computed(() => statusSignal.value === "loading");
510
539
  const accessSetSignal = accessControl ? signal(null) : null;
@@ -565,6 +594,10 @@ function AuthProvider({
565
594
  onSuccess: handleAuthSuccess
566
595
  });
567
596
  const signIn = Object.assign(async (body) => {
597
+ if (deferredRefreshTimer) {
598
+ clearTimeout(deferredRefreshTimer);
599
+ deferredRefreshTimer = null;
600
+ }
568
601
  statusSignal.value = "loading";
569
602
  errorSignal.value = null;
570
603
  const result = await signInMethod(body);
@@ -585,6 +618,10 @@ function AuthProvider({
585
618
  onSuccess: handleAuthSuccess
586
619
  });
587
620
  const signUp = Object.assign(async (body) => {
621
+ if (deferredRefreshTimer) {
622
+ clearTimeout(deferredRefreshTimer);
623
+ deferredRefreshTimer = null;
624
+ }
588
625
  statusSignal.value = "loading";
589
626
  errorSignal.value = null;
590
627
  const result = await signUpMethod(body);
@@ -645,7 +682,7 @@ function AuthProvider({
645
682
  method: resetPasswordMethod.method,
646
683
  meta: resetPasswordMethod.meta
647
684
  });
648
- const signOut = async () => {
685
+ const signOut = async (options) => {
649
686
  tokenRefresh.cancel();
650
687
  try {
651
688
  await fetch(`${basePath}/signout`, {
@@ -658,9 +695,16 @@ function AuthProvider({
658
695
  statusSignal.value = "unauthenticated";
659
696
  errorSignal.value = null;
660
697
  clearAccessSet();
661
- if (typeof window !== "undefined") {
698
+ if (isBrowser()) {
662
699
  delete window.__VERTZ_SESSION__;
663
700
  }
701
+ if (options?.redirectTo) {
702
+ if (router) {
703
+ router.navigate({ to: options.redirectTo, replace: true }).catch(() => {});
704
+ } else if (typeof console !== "undefined") {
705
+ console.warn("[vertz] signOut({ redirectTo }) was called but no RouterContext is available. Navigation was skipped.");
706
+ }
707
+ }
664
708
  };
665
709
  let refreshInFlight = null;
666
710
  const doRefresh = async () => {
@@ -730,9 +774,10 @@ function AuthProvider({
730
774
  refresh,
731
775
  mfaChallenge,
732
776
  forgotPassword,
733
- resetPassword
777
+ resetPassword,
778
+ providers: providersSignal
734
779
  };
735
- if (typeof window !== "undefined") {
780
+ if (isBrowser()) {
736
781
  if (window.__VERTZ_SESSION__?.user) {
737
782
  const session = window.__VERTZ_SESSION__;
738
783
  userSignal.value = session.user;
@@ -741,11 +786,14 @@ function AuthProvider({
741
786
  tokenRefresh.schedule(session.expiresAt);
742
787
  }
743
788
  } else {
744
- statusSignal.value = "unauthenticated";
789
+ deferredRefreshTimer = setTimeout(() => {
790
+ deferredRefreshTimer = null;
791
+ refresh();
792
+ }, 0);
745
793
  }
746
794
  }
747
795
  if (accessControl && accessSetSignal && accessLoadingSignal) {
748
- if (typeof window !== "undefined" && window.__VERTZ_ACCESS_SET__ && typeof window.__VERTZ_ACCESS_SET__.entitlements === "object" && window.__VERTZ_ACCESS_SET__.entitlements !== null) {
796
+ if (isBrowser() && window.__VERTZ_ACCESS_SET__ && typeof window.__VERTZ_ACCESS_SET__.entitlements === "object" && window.__VERTZ_ACCESS_SET__.entitlements !== null) {
749
797
  accessSetSignal.value = window.__VERTZ_ACCESS_SET__;
750
798
  accessLoadingSignal.value = false;
751
799
  }
@@ -781,18 +829,146 @@ function AuthGate({ fallback, children }) {
781
829
  function createAccessProvider() {
782
830
  const accessSet = signal(null);
783
831
  const loading = signal(true);
784
- if (typeof window !== "undefined" && window.__VERTZ_ACCESS_SET__ && typeof window.__VERTZ_ACCESS_SET__.entitlements === "object" && window.__VERTZ_ACCESS_SET__.entitlements !== null) {
832
+ if (isBrowser() && window.__VERTZ_ACCESS_SET__ && typeof window.__VERTZ_ACCESS_SET__.entitlements === "object" && window.__VERTZ_ACCESS_SET__.entitlements !== null) {
785
833
  accessSet.value = window.__VERTZ_ACCESS_SET__;
786
834
  loading.value = false;
787
835
  }
788
836
  return { accessSet, loading };
789
837
  }
838
+ // src/auth/provider-icons.ts
839
+ var icons = {
840
+ github: (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12Z"/></svg>`,
841
+ google: (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1Z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23Z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62Z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53Z"/></svg>`,
842
+ discord: (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03ZM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418Zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418Z"/></svg>`,
843
+ apple: (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="currentColor"><path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09ZM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.32 2.32-1.55 4.25-3.74 4.25Z"/></svg>`,
844
+ microsoft: (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24"><rect fill="#F25022" x="1" y="1" width="10" height="10"/><rect fill="#7FBA00" x="13" y="1" width="10" height="10"/><rect fill="#00A4EF" x="1" y="13" width="10" height="10"/><rect fill="#FFB900" x="13" y="13" width="10" height="10"/></svg>`,
845
+ twitter: (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>`
846
+ };
847
+ function fallbackIcon(size) {
848
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg>`;
849
+ }
850
+ function getProviderIcon(providerId, size) {
851
+ const iconFn = icons[providerId];
852
+ return iconFn ? iconFn(size) : fallbackIcon(size);
853
+ }
854
+
855
+ // src/auth/oauth-button.ts
856
+ var DANGEROUS_SCHEMES = ["javascript:", "data:", "vbscript:"];
857
+ function isSafeUrl(url) {
858
+ const normalized = url.replace(/\s/g, "").toLowerCase();
859
+ if (normalized.startsWith("//"))
860
+ return false;
861
+ for (const scheme of DANGEROUS_SCHEMES) {
862
+ if (normalized.startsWith(scheme))
863
+ return false;
864
+ }
865
+ return true;
866
+ }
867
+ function OAuthButton({ provider, label, iconOnly, _providers }) {
868
+ const providers = _providers ?? useAuth().providers;
869
+ const providerInfo = providers.find((p) => p.id === provider);
870
+ if (!providerInfo) {
871
+ return __element("span");
872
+ }
873
+ const safeAuthUrl = isSafeUrl(providerInfo.authUrl) ? providerInfo.authUrl : "#";
874
+ const props = { type: "button" };
875
+ if (iconOnly) {
876
+ props["aria-label"] = `Continue with ${providerInfo.name}`;
877
+ }
878
+ const el = __element("button", props);
879
+ __on(el, "click", () => {
880
+ window.location.href = safeAuthUrl;
881
+ });
882
+ __enterChildren(el);
883
+ const iconSpan = __element("span");
884
+ iconSpan.innerHTML = getProviderIcon(provider, 20);
885
+ __append(el, iconSpan);
886
+ if (!iconOnly) {
887
+ const text = label ?? `Continue with ${providerInfo.name}`;
888
+ const textSpan = __element("span");
889
+ __enterChildren(textSpan);
890
+ __append(textSpan, __staticText(text));
891
+ __exitChildren();
892
+ __append(el, textSpan);
893
+ }
894
+ __exitChildren();
895
+ return el;
896
+ }
897
+ // src/auth/oauth-buttons.ts
898
+ function OAuthButtons({ _providers } = {}) {
899
+ const providers = _providers ?? useAuth().providers;
900
+ const container = __element("div");
901
+ __enterChildren(container);
902
+ for (const provider of providers) {
903
+ const button = OAuthButton({
904
+ provider: provider.id,
905
+ _providers: providers
906
+ });
907
+ __append(container, button);
908
+ }
909
+ __exitChildren();
910
+ return container;
911
+ }
912
+ // src/auth/protected-route.ts
913
+ var __DEV__2 = typeof process !== "undefined" && true;
914
+ function ProtectedRoute({
915
+ loginPath = "/login",
916
+ fallback,
917
+ children,
918
+ requires,
919
+ forbidden,
920
+ returnTo = true
921
+ }) {
922
+ const ctx = useContext(AuthContext);
923
+ if (!ctx) {
924
+ if (__DEV__2) {
925
+ console.warn("ProtectedRoute used without AuthProvider — rendering children unprotected");
926
+ }
927
+ return typeof children === "function" ? children() : children;
928
+ }
929
+ const router = useContext(RouterContext);
930
+ const checks = requires?.map((e) => can(e));
931
+ const allAllowed = computed(() => !checks || checks.every((c) => c.allowed.value));
932
+ const isResolved = computed(() => {
933
+ const status = ctx.status;
934
+ return status !== "idle" && status !== "loading";
935
+ });
936
+ const shouldRedirect = computed(() => {
937
+ if (!isResolved.value)
938
+ return false;
939
+ return !ctx.isAuthenticated;
940
+ });
941
+ if (router) {
942
+ domEffect(() => {
943
+ if (shouldRedirect.value) {
944
+ const search = returnTo && isBrowser() ? `?returnTo=${encodeURIComponent(window.location.pathname + window.location.search)}` : "";
945
+ router.navigate({ to: `${loginPath}${search}`, replace: true });
946
+ }
947
+ });
948
+ }
949
+ return computed(() => {
950
+ if (!isResolved.value) {
951
+ return fallback ? fallback() : null;
952
+ }
953
+ if (shouldRedirect.value) {
954
+ return fallback ? fallback() : null;
955
+ }
956
+ if (!allAllowed.value) {
957
+ return forbidden ? forbidden() : null;
958
+ }
959
+ return typeof children === "function" ? children() : children;
960
+ });
961
+ }
790
962
  export {
791
963
  useAuth,
792
964
  useAccessContext,
965
+ getProviderIcon,
793
966
  createAccessProvider,
794
967
  createAccessEventClient,
795
968
  can,
969
+ ProtectedRoute,
970
+ OAuthButtons,
971
+ OAuthButton,
796
972
  AuthProvider,
797
973
  AuthGate,
798
974
  AuthContext,
@@ -27,6 +27,101 @@ type CSSOutput<T extends CSSInput = CSSInput> = { readonly [K in keyof T & strin
27
27
  * @returns Object with block names as keys (class name strings) and non-enumerable `css` property.
28
28
  */
29
29
  declare function css<T extends CSSInput>(input: T & { [K in keyof T & "css"]? : never }, filePath?: string): CSSOutput<T>;
30
+ interface FontSrc {
31
+ path: string;
32
+ weight?: string | number;
33
+ style?: "normal" | "italic";
34
+ }
35
+ type FallbackFontName = "Arial" | "Times New Roman" | "Courier New";
36
+ interface FontFallbackMetrics {
37
+ /** CSS ascent-override value, e.g., '94.52%' */
38
+ ascentOverride: string;
39
+ /** CSS descent-override value, e.g., '24.60%' */
40
+ descentOverride: string;
41
+ /** CSS line-gap-override value, e.g., '0.00%' */
42
+ lineGapOverride: string;
43
+ /** CSS size-adjust value, e.g., '104.88%' */
44
+ sizeAdjust: string;
45
+ /** System font used as fallback base. */
46
+ fallbackFont: FallbackFontName;
47
+ }
48
+ interface CompileFontsOptions {
49
+ /** Pre-computed fallback metrics per font key. Provided by @vertz/ui-server at build/SSR time. */
50
+ fallbackMetrics?: Record<string, FontFallbackMetrics>;
51
+ }
52
+ interface FontOptions {
53
+ /** Font weight: '100..1000' (variable) or 400 (fixed). */
54
+ weight: string | number;
55
+ /** Font style. @default 'normal' */
56
+ style?: "normal" | "italic";
57
+ /** Font-display strategy. @default 'swap' */
58
+ display?: "auto" | "block" | "swap" | "fallback" | "optional";
59
+ /** URL path(s) for local/self-hosted fonts. */
60
+ src?: string | FontSrc[];
61
+ /** Fallback font stack. */
62
+ fallback?: string[];
63
+ /** Font subsets (metadata only — subsetting is deferred to a future phase). @default ['latin'] */
64
+ subsets?: string[];
65
+ /** Unicode range for subsetting. */
66
+ unicodeRange?: string;
67
+ /**
68
+ * Control automatic fallback font metric adjustment for zero-CLS font loading.
69
+ * - true: auto-detect fallback base from `fallback` array (default)
70
+ * - false: disable
71
+ * - 'Arial' | 'Times New Roman' | 'Courier New': explicit base
72
+ * @default true
73
+ */
74
+ adjustFontFallback?: boolean | FallbackFontName;
75
+ }
76
+ type FontStyle = "normal" | "italic";
77
+ type FontDisplay = "auto" | "block" | "swap" | "fallback" | "optional";
78
+ interface FontDescriptor {
79
+ readonly __brand: "FontDescriptor";
80
+ readonly family: string;
81
+ readonly weight: string;
82
+ readonly style: FontStyle;
83
+ readonly display: FontDisplay;
84
+ readonly src?: string | FontSrc[];
85
+ readonly fallback: string[];
86
+ readonly subsets: string[];
87
+ readonly unicodeRange?: string;
88
+ readonly adjustFontFallback: boolean | FallbackFontName;
89
+ }
90
+ /** Structured description of a resource to preload. */
91
+ interface PreloadItem {
92
+ href: string;
93
+ as: "font" | "image" | "style" | "script";
94
+ type?: string;
95
+ crossorigin?: boolean;
96
+ }
97
+ interface CompiledFonts {
98
+ /** @font-face declarations. */
99
+ fontFaceCss: string;
100
+ /** :root { --font-<key>: ...; } block (for standalone use). */
101
+ cssVarsCss: string;
102
+ /** Individual CSS var lines (e.g., ' --font-sans: ...;') for merging into an existing :root. */
103
+ cssVarLines: string[];
104
+ /** <link rel="preload"> HTML tags for font files. */
105
+ preloadTags: string;
106
+ /** Structured preload data for generating HTTP Link headers. */
107
+ preloadItems: PreloadItem[];
108
+ }
109
+ /**
110
+ * Create a font descriptor for use in theme definitions.
111
+ *
112
+ * @param family - The font family name (e.g., 'DM Sans').
113
+ * @param options - Font configuration.
114
+ * @returns A FontDescriptor.
115
+ */
116
+ declare function font(family: string, options: FontOptions): FontDescriptor;
117
+ /**
118
+ * Compile font descriptors into CSS and preload tags.
119
+ *
120
+ * @param fonts - A map of token key → FontDescriptor.
121
+ * @param options - Optional compilation settings (e.g., pre-computed fallback metrics).
122
+ * @returns Compiled @font-face CSS, CSS var lines, and preload link tags.
123
+ */
124
+ declare function compileFonts(fonts: Record<string, FontDescriptor>, options?: CompileFontsOptions): CompiledFonts;
30
125
  /** Input to globalCss(): selector → property-value map. */
31
126
  type GlobalCSSInput = Record<string, Record<string, string>>;
32
127
  /** Output of globalCss(): extracted CSS string. */
@@ -65,6 +160,8 @@ interface ThemeInput {
65
160
  colors: ColorTokens;
66
161
  /** Spacing scale tokens. */
67
162
  spacing?: SpacingTokens;
163
+ /** Font descriptors keyed by token name (e.g., sans, mono, display). */
164
+ fonts?: Record<string, FontDescriptor>;
68
165
  }
69
166
  /** The structured theme object returned by defineTheme(). */
70
167
  interface Theme {
@@ -72,6 +169,8 @@ interface Theme {
72
169
  colors: ColorTokens;
73
170
  /** Spacing scale tokens. */
74
171
  spacing?: SpacingTokens;
172
+ /** Font descriptors keyed by token name. */
173
+ fonts?: Record<string, FontDescriptor>;
75
174
  }
76
175
  /** Output of compileTheme(). */
77
176
  interface CompiledTheme {
@@ -79,6 +178,15 @@ interface CompiledTheme {
79
178
  css: string;
80
179
  /** Flat list of token dot-paths (e.g., 'primary.500', 'background'). */
81
180
  tokens: string[];
181
+ /** Font preload link tags for injection into <head>. */
182
+ preloadTags: string;
183
+ /** Structured preload data for generating HTTP Link headers. */
184
+ preloadItems: PreloadItem[];
185
+ }
186
+ /** Options for compileTheme(). */
187
+ interface CompileThemeOptions {
188
+ /** Pre-computed font fallback metrics for zero-CLS font loading. */
189
+ fallbackMetrics?: CompileFontsOptions["fallbackMetrics"];
82
190
  }
83
191
  /**
84
192
  * Define a theme with raw and contextual design tokens.
@@ -97,7 +205,7 @@ declare function defineTheme(input: ThemeInput): Theme;
97
205
  * @param theme - A theme object from defineTheme().
98
206
  * @returns Compiled CSS and token list.
99
207
  */
100
- declare function compileTheme(theme: Theme): CompiledTheme;
208
+ declare function compileTheme(theme: Theme, options?: CompileThemeOptions): CompiledTheme;
101
209
  /** A child node: either a DOM Node or a string (text content). */
102
210
  type ThemeChild = Node | string;
103
211
  /** Props for ThemeProvider. */
@@ -151,4 +259,4 @@ interface VariantFunction<V extends VariantDefinitions> {
151
259
  * @returns A function that accepts variant props and returns a className string.
152
260
  */
153
261
  declare function variants<V extends VariantDefinitions>(config: VariantsConfig<V>): VariantFunction<V>;
154
- export { variants, s, globalCss, defineTheme, css, compileTheme, VariantsConfig, VariantProps, VariantFunction, ThemeProviderProps, ThemeProvider, ThemeInput, Theme, StyleEntry, GlobalCSSOutput, GlobalCSSInput, CompiledTheme, CSSOutput, CSSInput };
262
+ export { variants, s, globalCss, font, defineTheme, css, compileTheme, compileFonts, VariantsConfig, VariantProps, VariantFunction, ThemeProviderProps, ThemeProvider, ThemeInput, Theme, StyleEntry, PreloadItem, GlobalCSSOutput, GlobalCSSInput, FontSrc, FontOptions, FontFallbackMetrics, FontDescriptor, FallbackFontName, CompiledTheme, CompiledFonts, CompileThemeOptions, CompileFontsOptions, CSSOutput, CSSInput };
@@ -1,22 +1,26 @@
1
1
  import {
2
2
  ThemeProvider,
3
+ compileFonts,
3
4
  compileTheme,
4
5
  css,
5
6
  defineTheme,
7
+ font,
6
8
  globalCss,
7
9
  s,
8
10
  variants
9
- } from "../../shared/chunk-1wby7nex.js";
10
- import"../../shared/chunk-j6qyxfdc.js";
11
+ } from "../../shared/chunk-dhehvmj0.js";
12
+ import"../../shared/chunk-vndfjfdy.js";
11
13
  import"../../shared/chunk-prj7nm08.js";
12
- import"../../shared/chunk-h89w580h.js";
13
- import"../../shared/chunk-8hsz5y4a.js";
14
+ import"../../shared/chunk-afawz764.js";
15
+ import"../../shared/chunk-4fwcwxn6.js";
14
16
  export {
15
17
  variants,
16
18
  s,
17
19
  globalCss,
20
+ font,
18
21
  defineTheme,
19
22
  css,
20
23
  compileTheme,
24
+ compileFonts,
21
25
  ThemeProvider
22
26
  };
@@ -90,8 +90,29 @@ interface SdkMethodWithMeta<
90
90
  }
91
91
  /** Reserved property names that cannot be used as field names on FormInstance. */
92
92
  type ReservedFormNames = "submitting" | "dirty" | "valid" | "action" | "method" | "onSubmit" | "reset" | "setFieldError" | "submit" | "fields" | "__bindElement";
93
- /** Mapped type providing FieldState for each field in TBody. */
93
+ /** Mapped type providing FieldState for each field in TBody (flat — no nested access). */
94
94
  type FieldAccessors<TBody> = { [K in keyof TBody] : FieldState<TBody[K]> };
95
+ /** Built-in object types that should NOT recurse into NestedFieldAccessors. */
96
+ type BuiltInObjects = Date | RegExp | File | Blob | Map<unknown, unknown> | Set<unknown>;
97
+ /** FieldState signal/method property names that conflict with nested field names. */
98
+ type FieldSignalReservedNames = "error" | "dirty" | "touched" | "value" | "setValue" | "reset";
99
+ /** Check if any key of T collides with FieldState property names. */
100
+ type HasReservedFieldName<T> = keyof T & FieldSignalReservedNames extends never ? false : true;
101
+ /** Recursive field accessors for nested schemas. */
102
+ type NestedFieldAccessors<T> = { [K in keyof T] : T[K] extends BuiltInObjects ? FieldState<T[K]> : T[K] extends Array<infer U> ? FieldState<T[K]> & ArrayFieldAccessors<U> : T[K] extends Record<string, unknown> ? HasReservedFieldName<T[K]> extends true ? {
103
+ __error: `Nested field name conflicts with FieldState property: ${keyof T[K] & FieldSignalReservedNames & string}`;
104
+ } : FieldState<T[K]> & NestedFieldAccessors<T[K]> : FieldState<T[K]> };
105
+ /** Array field accessors — numeric index access to element-level fields. */
106
+ type ArrayFieldAccessors<U> = {
107
+ [index: number]: U extends BuiltInObjects ? FieldState<U> : U extends Record<string, unknown> ? FieldState<U> & NestedFieldAccessors<U> : FieldState<U>;
108
+ };
109
+ /** Deep partial utility for initial values. */
110
+ type DeepPartial<T> = { [K in keyof T]? : T[K] extends Array<infer U> ? Array<DeepPartial<U>> : T[K] extends Record<string, unknown> ? DeepPartial<T[K]> : T[K] };
111
+ /** Union of all valid dot-paths through a nested type. */
112
+ type FieldPath<
113
+ T,
114
+ Prefix extends string = ""
115
+ > = `${Prefix}${keyof T & string}` | { [K in keyof T & string] : T[K] extends Record<string, unknown> ? FieldPath<T[K], `${Prefix}${K}.`> : never }[keyof T & string];
95
116
  /** Mapped type providing typed field name strings for compile-time input validation. */
96
117
  type FieldNames<TBody> = { readonly [K in keyof TBody & string] : K };
97
118
  /** Base properties available on every form instance. */
@@ -100,7 +121,7 @@ interface FormBaseProperties<TBody> {
100
121
  method: string;
101
122
  onSubmit: (e: Event) => Promise<void>;
102
123
  reset: () => void;
103
- setFieldError: (field: keyof TBody & string, message: string) => void;
124
+ setFieldError: (field: FieldPath<TBody>, message: string) => void;
104
125
  submit: (formData?: FormData) => Promise<void>;
105
126
  submitting: Signal<boolean>;
106
127
  dirty: ReadonlySignal<boolean>;
@@ -117,7 +138,7 @@ interface FormBaseProperties<TBody> {
117
138
  type FormInstance<
118
139
  TBody,
119
140
  _TResult
120
- > = keyof TBody & ReservedFormNames extends never ? FormBaseProperties<TBody> & FieldAccessors<TBody> : {
141
+ > = keyof TBody & ReservedFormNames extends never ? FormBaseProperties<TBody> & NestedFieldAccessors<TBody> : {
121
142
  __error: `Field name conflicts with reserved form property: ${keyof TBody & ReservedFormNames & string}`;
122
143
  };
123
144
  /** Options for creating a form instance. */
@@ -127,8 +148,8 @@ interface FormOptions<
127
148
  > {
128
149
  /** Explicit schema for client-side validation before submission. */
129
150
  schema?: FormSchema<TBody>;
130
- /** Initial values for form fields. */
131
- initial?: Partial<TBody> | (() => Partial<TBody>);
151
+ /** Initial values for form fields. Supports nested partial values. */
152
+ initial?: DeepPartial<TBody> | (() => DeepPartial<TBody>);
132
153
  /** Callback invoked after a successful submission. */
133
154
  onSuccess?: (result: TResult) => void;
134
155
  /** Callback invoked when validation or submission fails. */
@@ -157,6 +178,8 @@ declare function form<
157
178
  interface FormDataOptions {
158
179
  /** When true, coerces numeric strings to numbers and "true"/"false" to booleans. */
159
180
  coerce?: boolean;
181
+ /** When true, parses dot-separated keys into nested objects (e.g., "address.street" → { address: { street: ... } }). */
182
+ nested?: boolean;
160
183
  }
161
184
  /**
162
185
  * Convert FormData to a plain object.
@@ -167,4 +190,4 @@ interface FormDataOptions {
167
190
  * "true"/"false" become booleans.
168
191
  */
169
192
  declare function formDataToObject(formData: FormData, options?: FormDataOptions): Record<string, unknown>;
170
- export { validate, formDataToObject, form, createFieldState, ValidationResult, SdkMethodWithMeta, SdkMethod, FormSchema, FormOptions, FormInstance, FormDataOptions, FieldState, FieldNames };
193
+ export { validate, formDataToObject, form, createFieldState, ValidationResult, SdkMethodWithMeta, SdkMethod, NestedFieldAccessors, FormSchema, FormOptions, FormInstance, FormDataOptions, FieldState, FieldPath, FieldNames, FieldAccessors, DeepPartial, ArrayFieldAccessors };