@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.
- package/README.md +49 -0
- package/dist/shared/chunk-14eqne2a.js +10 -0
- package/dist/shared/{chunk-dksg08fq.js → chunk-1rxa2fz4.js} +2 -8
- package/dist/shared/{chunk-8hsz5y4a.js → chunk-4fwcwxn6.js} +14 -4
- package/dist/shared/{chunk-hw67ckr3.js → chunk-4mtn7af6.js} +230 -19
- package/dist/shared/{chunk-4txc67nd.js → chunk-6jyt4ycw.js} +67 -2
- package/dist/shared/{chunk-83g4h38e.js → chunk-6wd36w21.js} +1 -0
- package/dist/shared/{chunk-h89w580h.js → chunk-afawz764.js} +1 -1
- package/dist/shared/{chunk-nn9v1zmk.js → chunk-b0qqqk03.js} +86 -21
- package/dist/shared/{chunk-1wby7nex.js → chunk-dhehvmj0.js} +161 -9
- package/dist/shared/{chunk-wymw818z.js → chunk-fkbgbf3n.js} +48 -9
- package/dist/shared/{chunk-5dbq8jp9.js → chunk-j09yyh34.js} +72 -6
- package/dist/shared/chunk-mtsvrj9e.js +23 -0
- package/dist/shared/chunk-pnv25zep.js +7 -0
- package/dist/shared/{chunk-j6qyxfdc.js → chunk-vndfjfdy.js} +3 -3
- package/dist/src/auth/public.d.ts +56 -9
- package/dist/src/auth/public.js +189 -13
- package/dist/src/css/public.d.ts +110 -2
- package/dist/src/css/public.js +8 -4
- package/dist/src/form/public.d.ts +29 -6
- package/dist/src/form/public.js +2 -2
- package/dist/src/index.d.ts +284 -13
- package/dist/src/index.js +161 -14
- package/dist/src/internals.d.ts +141 -5
- package/dist/src/internals.js +17 -9
- package/dist/src/query/public.js +4 -3
- package/dist/src/router/public.d.ts +17 -4
- package/dist/src/router/public.js +17 -11
- package/dist/src/test/index.d.ts +5 -0
- package/dist/src/test/index.js +4 -3
- package/package.json +3 -3
- 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:
|
|
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
|
-
|
|
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 };
|
package/dist/src/auth/public.js
CHANGED
|
@@ -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-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 &&
|
|
498
|
+
if (visibilityHandler && isBrowser()) {
|
|
480
499
|
document.removeEventListener("visibilitychange", visibilityHandler);
|
|
481
500
|
}
|
|
482
|
-
if (onlineHandler &&
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
789
|
+
deferredRefreshTimer = setTimeout(() => {
|
|
790
|
+
deferredRefreshTimer = null;
|
|
791
|
+
refresh();
|
|
792
|
+
}, 0);
|
|
745
793
|
}
|
|
746
794
|
}
|
|
747
795
|
if (accessControl && accessSetSignal && accessLoadingSignal) {
|
|
748
|
-
if (
|
|
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 (
|
|
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,
|
package/dist/src/css/public.d.ts
CHANGED
|
@@ -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 };
|
package/dist/src/css/public.js
CHANGED
|
@@ -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-
|
|
10
|
-
import"../../shared/chunk-
|
|
11
|
+
} from "../../shared/chunk-dhehvmj0.js";
|
|
12
|
+
import"../../shared/chunk-vndfjfdy.js";
|
|
11
13
|
import"../../shared/chunk-prj7nm08.js";
|
|
12
|
-
import"../../shared/chunk-
|
|
13
|
-
import"../../shared/chunk-
|
|
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:
|
|
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> &
|
|
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?:
|
|
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 };
|