@vertz/ui 0.2.16 → 0.2.18

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.
@@ -39,4 +39,10 @@ function __classList(el, classMap) {
39
39
  };
40
40
  }
41
41
 
42
- export { __attr, __show, __classList };
42
+ // src/dom/events.ts
43
+ function __on(el, event, handler) {
44
+ el.addEventListener(event, handler);
45
+ return () => el.removeEventListener(event, handler);
46
+ }
47
+
48
+ export { __attr, __show, __classList, __on };
@@ -3,7 +3,7 @@ import {
3
3
  __element,
4
4
  __enterChildren,
5
5
  __exitChildren
6
- } from "./chunk-vndfjfdy.js";
6
+ } from "./chunk-mgfrrrjq.js";
7
7
  import {
8
8
  getSSRContext
9
9
  } from "./chunk-4fwcwxn6.js";
@@ -1,15 +1,7 @@
1
1
  import {
2
- __classList
3
- } from "./chunk-1rxa2fz4.js";
4
- import {
5
- RouterContext
6
- } from "./chunk-mtsvrj9e.js";
7
- import {
2
+ __classList,
8
3
  __on
9
- } from "./chunk-pnv25zep.js";
10
- import {
11
- isBrowser
12
- } from "./chunk-14eqne2a.js";
4
+ } from "./chunk-07bh4m1e.js";
13
5
  import {
14
6
  __append,
15
7
  __element,
@@ -17,7 +9,13 @@ import {
17
9
  __exitChildren,
18
10
  __staticText,
19
11
  getIsHydrating
20
- } from "./chunk-vndfjfdy.js";
12
+ } from "./chunk-mgfrrrjq.js";
13
+ import {
14
+ RouterContext
15
+ } from "./chunk-mtsvrj9e.js";
16
+ import {
17
+ isBrowser
18
+ } from "./chunk-14eqne2a.js";
21
19
  import {
22
20
  _tryOnCleanup,
23
21
  createContext,
@@ -149,7 +147,9 @@ var OutletContext = createContext(undefined, "@vertz/ui::OutletContext");
149
147
  function Outlet() {
150
148
  const ctx = useContext(OutletContext);
151
149
  if (!ctx) {
152
- return document.createComment("outlet:empty");
150
+ const el = __element("span");
151
+ el.style.display = "contents";
152
+ return el;
153
153
  }
154
154
  const container = __element("div");
155
155
  let childCleanups = [];
@@ -254,6 +254,11 @@ function __child(fn) {
254
254
  if (isRenderNode(value) && wrapper.childNodes.length === 1 && wrapper.firstChild === value) {
255
255
  return;
256
256
  }
257
+ if (!isRenderNode(value) && value != null && typeof value !== "boolean" && wrapper.childNodes.length === 1 && wrapper.firstChild.nodeType === 3) {
258
+ const text = typeof value === "string" ? value : String(value);
259
+ wrapper.firstChild.data = text;
260
+ return;
261
+ }
257
262
  while (wrapper.firstChild) {
258
263
  wrapper.removeChild(wrapper.firstChild);
259
264
  }
@@ -272,6 +277,11 @@ function __child(fn) {
272
277
  if (isRenderNode(value) && wrapper.childNodes.length === 1 && wrapper.firstChild === value) {
273
278
  return;
274
279
  }
280
+ if (!isRenderNode(value) && value != null && typeof value !== "boolean" && typeof value !== "function" && wrapper.childNodes.length === 1 && wrapper.firstChild.nodeType === 3) {
281
+ const text = typeof value === "string" ? value : String(value);
282
+ wrapper.firstChild.data = text;
283
+ return;
284
+ }
275
285
  while (wrapper.firstChild) {
276
286
  wrapper.removeChild(wrapper.firstChild);
277
287
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  injectCSS
3
- } from "./chunk-dhehvmj0.js";
3
+ } from "./chunk-c3r237f0.js";
4
4
 
5
5
  // src/dom/animation.ts
6
6
  function onAnimationsComplete(el, callback) {
@@ -121,6 +121,17 @@ interface EntitlementRegistry {}
121
121
  /** Entitlement type — narrows to literal union when codegen populates EntitlementRegistry. */
122
122
  type Entitlement = keyof EntitlementRegistry extends never ? string : Extract<keyof EntitlementRegistry, string>;
123
123
  /**
124
+ * @internal Signal-backed version of AccessCheck — actual runtime type of can() return.
125
+ * Use with canSignals() when framework code needs reactive signal access without compiler transforms.
126
+ */
127
+ interface RawAccessCheck {
128
+ readonly allowed: ReadonlySignal<boolean>;
129
+ readonly reasons: ReadonlySignal<DenialReason[]>;
130
+ readonly reason: ReadonlySignal<DenialReason | undefined>;
131
+ readonly meta: ReadonlySignal<DenialMeta | undefined>;
132
+ readonly loading: ReadonlySignal<boolean>;
133
+ }
134
+ /**
124
135
  * Check if the current user has a specific entitlement.
125
136
  *
126
137
  * Must be called in the component body (like query()/form()).
@@ -136,6 +147,19 @@ type Entitlement = keyof EntitlementRegistry extends never ? string : Extract<ke
136
147
  declare function can(entitlement: Entitlement, entity?: {
137
148
  __access?: Record<string, AccessCheckData>;
138
149
  }): AccessCheck;
150
+ /**
151
+ * @internal Framework use only.
152
+ * Same as can() but returns raw ReadonlySignal properties.
153
+ * Use when framework code needs reactive signal access without compiler transforms.
154
+ *
155
+ * Must NOT be added to the signal-api-registry — the compiler must not auto-unwrap these.
156
+ *
157
+ * @param entitlement - The entitlement to check
158
+ * @param entity - Optional entity with pre-computed `__access` metadata
159
+ */
160
+ declare function canSignals(entitlement: Entitlement, entity?: {
161
+ __access?: Record<string, AccessCheckData>;
162
+ }): RawAccessCheck;
139
163
  /** Client-side access events (no orgId/userId — server scopes the broadcast) */
140
164
  type ClientAccessEvent = {
141
165
  type: "access:flag_toggled";
@@ -179,16 +203,6 @@ interface AccessEventClient {
179
203
  dispose(): void;
180
204
  }
181
205
  declare function createAccessEventClient(options: AccessEventClientOptions): AccessEventClient;
182
- interface AccessGateProps {
183
- fallback?: () => unknown;
184
- children: (() => unknown) | unknown;
185
- }
186
- /**
187
- * Gate component that blocks children while the access set is loading.
188
- * Use this to prevent flicker on initial render when access data
189
- * hasn't been hydrated yet.
190
- */
191
- declare function AccessGate({ fallback, children }: AccessGateProps): ReadonlySignal<unknown> | unknown;
192
206
  import { Result } from "@vertz/fetch";
193
207
  /**
194
208
  * Minimal schema interface compatible with @vertz/schema.
@@ -306,11 +320,6 @@ interface AuthProviderProps {
306
320
  children: (() => unknown) | unknown;
307
321
  }
308
322
  declare function AuthProvider({ basePath, accessControl, accessEvents, accessEventsUrl, flagEntitlementMap, children }: AuthProviderProps): HTMLElement;
309
- interface AuthGateProps {
310
- fallback?: () => unknown;
311
- children: (() => unknown) | unknown;
312
- }
313
- declare function AuthGate({ fallback, children }: AuthGateProps): ReadonlySignal<unknown> | unknown;
314
323
  /**
315
324
  * Create an AccessContextValue for use with AccessContext.Provider.
316
325
  * Hydrates from `window.__VERTZ_ACCESS_SET__` when available (SSR).
@@ -324,40 +333,24 @@ declare function AuthGate({ fallback, children }: AuthGateProps): ReadonlySignal
324
333
  * ```
325
334
  */
326
335
  declare function createAccessProvider(): AccessContextValue;
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
336
  /**
359
337
  * Get an SVG icon string for a provider.
360
338
  * Returns the built-in icon for known providers, or a generic fallback for unknown.
361
339
  */
362
340
  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 };
341
+ /**
342
+ * Get a display name for a user with a fallback chain.
343
+ * Chain: user.name (if string & non-empty) → user.email → fallback (default: 'Unknown')
344
+ */
345
+ declare function getUserDisplayName(user: User | null | undefined, fallback?: string): string;
346
+ /**
347
+ * Get initials from a user's name or email (max 2 characters).
348
+ * Chain: first + last word initials from name → first char of email → '?'
349
+ */
350
+ declare function getUserInitials(user: User | null | undefined): string;
351
+ /**
352
+ * Default user silhouette icon for avatar fallbacks.
353
+ * Returns an inline SVG string — no external requests, works in SSR.
354
+ */
355
+ declare function getUserIcon(size: number): string;
356
+ export { useAuth, useAccessContext, getUserInitials, getUserIcon, getUserDisplayName, getProviderIcon, createAccessProvider, createAccessEventClient, canSignals, can, User, SignUpInput, SignOutOptions, SignInInput, ResetInput, RawAccessCheck, OAuthProviderInfo, MfaInput, ForgotInput, EntitlementRegistry, Entitlement, DenialReason, DenialMeta, ClientAccessEvent, AuthStatus, AuthResponse, AuthProviderProps, AuthProvider, AuthErrorCode, AuthContextValue, AuthContext, AuthClientError, AccessSet, AccessEventClientOptions, AccessEventClient, AccessContextValue, AccessContext, AccessCheckData, AccessCheck };
@@ -1,26 +1,14 @@
1
1
  import {
2
2
  RouterContext
3
3
  } from "../../shared/chunk-mtsvrj9e.js";
4
- import {
5
- __on
6
- } from "../../shared/chunk-pnv25zep.js";
7
4
  import {
8
5
  isBrowser
9
6
  } 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";
19
7
  import {
20
8
  _tryOnCleanup,
21
9
  computed,
22
10
  createContext,
23
- domEffect,
11
+ getSSRContext,
24
12
  signal,
25
13
  useContext
26
14
  } from "../../shared/chunk-4fwcwxn6.js";
@@ -34,25 +22,18 @@ function useAccessContext() {
34
22
  }
35
23
  return ctx;
36
24
  }
37
- function createFallbackDenied() {
38
- return {
39
- allowed: computed(() => false),
40
- reasons: computed(() => ["not_authenticated"]),
41
- reason: computed(() => "not_authenticated"),
42
- meta: computed(() => {
43
- return;
44
- }),
45
- loading: computed(() => false)
46
- };
47
- }
48
25
  var __DEV__ = typeof process !== "undefined" && true;
49
- function can(entitlement, entity) {
50
- const ctx = useContext(AccessContext);
26
+ function createAccessCheckRaw(ctx, entitlement, entity) {
51
27
  if (!ctx) {
52
- if (__DEV__) {
53
- console.warn("can() called without AccessContext.Provider — all checks denied");
54
- }
55
- return createFallbackDenied();
28
+ return {
29
+ allowed: computed(() => false),
30
+ reasons: computed(() => ["not_authenticated"]),
31
+ reason: computed(() => "not_authenticated"),
32
+ meta: computed(() => {
33
+ return;
34
+ }),
35
+ loading: computed(() => false)
36
+ };
56
37
  }
57
38
  const accessData = computed(() => {
58
39
  if (entity?.__access?.[entitlement])
@@ -73,6 +54,20 @@ function can(entitlement, entity) {
73
54
  })
74
55
  };
75
56
  }
57
+ function can(entitlement, entity) {
58
+ const ctx = useContext(AccessContext);
59
+ if (!ctx && __DEV__) {
60
+ console.warn("can() called without AccessContext.Provider — all checks denied");
61
+ }
62
+ return createAccessCheckRaw(ctx, entitlement, entity);
63
+ }
64
+ function canSignals(entitlement, entity) {
65
+ const ctx = useContext(AccessContext);
66
+ if (!ctx && __DEV__) {
67
+ console.warn("canSignals() called without AccessContext.Provider — all checks denied");
68
+ }
69
+ return createAccessCheckRaw(ctx, entitlement, entity);
70
+ }
76
71
  // src/auth/access-event-client.ts
77
72
  var BASE_BACKOFF_MS = 1000;
78
73
  var MAX_BACKOFF_MS = 30000;
@@ -158,26 +153,6 @@ function createAccessEventClient(options) {
158
153
  }
159
154
  return { connect, disconnect, dispose };
160
155
  }
161
- // src/auth/access-gate.ts
162
- function AccessGate({
163
- fallback,
164
- children
165
- }) {
166
- const ctx = useContext(AccessContext);
167
- if (!ctx) {
168
- return typeof children === "function" ? children() : children;
169
- }
170
- const isLoaded = computed(() => {
171
- const set = ctx.accessSet;
172
- return set !== null;
173
- });
174
- return computed(() => {
175
- if (isLoaded.value) {
176
- return typeof children === "function" ? children() : children;
177
- }
178
- return fallback ? fallback() : null;
179
- });
180
- }
181
156
  // src/auth/access-event-handler.ts
182
157
  function handleAccessEvent(accessSet, event, flagEntitlementMap) {
183
158
  const current = accessSet.value;
@@ -791,6 +766,16 @@ function AuthProvider({
791
766
  refresh();
792
767
  }, 0);
793
768
  }
769
+ } else {
770
+ const ssrCtx = getSSRContext();
771
+ if (ssrCtx?.ssrAuth) {
772
+ if (ssrCtx.ssrAuth.status === "authenticated") {
773
+ userSignal.value = ssrCtx.ssrAuth.user;
774
+ statusSignal.value = "authenticated";
775
+ } else {
776
+ statusSignal.value = "unauthenticated";
777
+ }
778
+ }
794
779
  }
795
780
  if (accessControl && accessSetSignal && accessLoadingSignal) {
796
781
  if (isBrowser() && window.__VERTZ_ACCESS_SET__ && typeof window.__VERTZ_ACCESS_SET__.entitlements === "object" && window.__VERTZ_ACCESS_SET__.entitlements !== null) {
@@ -808,23 +793,6 @@ function AuthProvider({
808
793
  }
809
794
  return AuthContext.Provider({ value: contextValue, children });
810
795
  }
811
- // src/auth/auth-gate.ts
812
- function AuthGate({ fallback, children }) {
813
- const ctx = useContext(AuthContext);
814
- if (!ctx) {
815
- return typeof children === "function" ? children() : children;
816
- }
817
- const isResolved = computed(() => {
818
- const status = ctx.status;
819
- return status !== "idle" && status !== "loading";
820
- });
821
- return computed(() => {
822
- if (isResolved.value) {
823
- return typeof children === "function" ? children() : children;
824
- }
825
- return fallback ? fallback() : null;
826
- });
827
- }
828
796
  // src/auth/create-access-provider.ts
829
797
  function createAccessProvider() {
830
798
  const accessSet = signal(null);
@@ -851,127 +819,49 @@ function getProviderIcon(providerId, size) {
851
819
  const iconFn = icons[providerId];
852
820
  return iconFn ? iconFn(size) : fallbackIcon(size);
853
821
  }
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;
822
+ // src/auth/user-display.ts
823
+ function getUserDisplayName(user, fallback = "Unknown") {
824
+ if (!user)
825
+ return fallback;
826
+ const name = user.name;
827
+ if (typeof name === "string" && name.trim().length > 0)
828
+ return name.trim();
829
+ if (user.email)
830
+ return user.email;
831
+ return fallback;
896
832
  }
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;
833
+ function getUserInitials(user) {
834
+ if (!user)
835
+ return "?";
836
+ const name = user.name;
837
+ if (typeof name === "string" && name.trim().length > 0) {
838
+ const words = name.trim().split(/\s+/);
839
+ const first = words[0] ?? "";
840
+ const last = words[words.length - 1] ?? "";
841
+ if (words.length === 1 || !last)
842
+ return first.charAt(0).toUpperCase() || "?";
843
+ return (first.charAt(0) + last.charAt(0)).toUpperCase();
844
+ }
845
+ if (user.email && user.email.length > 0)
846
+ return user.email.charAt(0).toUpperCase();
847
+ return "?";
911
848
  }
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
- });
849
+ // src/auth/user-icon.ts
850
+ function getUserIcon(size) {
851
+ 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="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;
961
852
  }
962
853
  export {
963
854
  useAuth,
964
855
  useAccessContext,
856
+ getUserInitials,
857
+ getUserIcon,
858
+ getUserDisplayName,
965
859
  getProviderIcon,
966
860
  createAccessProvider,
967
861
  createAccessEventClient,
862
+ canSignals,
968
863
  can,
969
- ProtectedRoute,
970
- OAuthButtons,
971
- OAuthButton,
972
864
  AuthProvider,
973
- AuthGate,
974
865
  AuthContext,
975
- AccessGate,
976
866
  AccessContext
977
867
  };
@@ -8,8 +8,8 @@ import {
8
8
  globalCss,
9
9
  s,
10
10
  variants
11
- } from "../../shared/chunk-dhehvmj0.js";
12
- import"../../shared/chunk-vndfjfdy.js";
11
+ } from "../../shared/chunk-c3r237f0.js";
12
+ import"../../shared/chunk-mgfrrrjq.js";
13
13
  import"../../shared/chunk-prj7nm08.js";
14
14
  import"../../shared/chunk-afawz764.js";
15
15
  import"../../shared/chunk-4fwcwxn6.js";
@@ -117,12 +117,14 @@ declare function createContext<T>(defaultValue?: T, __stableId?: string): Contex
117
117
  * Returns the default value if no Provider is active.
118
118
  */
119
119
  declare function useContext<T>(ctx: Context<T>): UnwrapSignals<T> | undefined;
120
+ /** DOM element types accepted by JSX (mirrors JSX.Element). */
121
+ type JsxElement = HTMLElement | SVGElement | DocumentFragment;
120
122
  /** Props for the ErrorBoundary component. */
121
123
  interface ErrorBoundaryProps {
122
124
  /** Function that returns the children to render. */
123
- children: () => Node;
125
+ children: () => JsxElement;
124
126
  /** Fallback renderer that receives the caught error and a retry function. */
125
- fallback: (error: Error, retry: () => void) => Node;
127
+ fallback: (error: Error, retry: () => void) => JsxElement;
126
128
  }
127
129
  /**
128
130
  * ErrorBoundary component.
@@ -135,7 +137,7 @@ interface ErrorBoundaryProps {
135
137
  * Also registers an async error handler so that nested Suspense components
136
138
  * can propagate async errors to this boundary.
137
139
  */
138
- declare function ErrorBoundary(props: ErrorBoundaryProps): Node;
140
+ declare function ErrorBoundary(props: ErrorBoundaryProps): JsxElement;
139
141
  /**
140
142
  * Runs callback once on mount. Never re-executes.
141
143
  * Return a function to register cleanup that runs on unmount.
@@ -184,12 +186,14 @@ interface Ref<T> {
184
186
  * `ref.current` will hold the DOM element.
185
187
  */
186
188
  declare function ref<T>(): Ref<T>;
189
+ /** DOM element types accepted by JSX (mirrors JSX.Element). */
190
+ type JsxElement2 = HTMLElement | SVGElement | DocumentFragment;
187
191
  /** Props for the Suspense component. */
188
192
  interface SuspenseProps {
189
193
  /** Function that returns the children to render (may throw a Promise). */
190
- children: () => Node;
194
+ children: () => JsxElement2;
191
195
  /** Fallback renderer shown while children are pending. */
192
- fallback: () => Node;
196
+ fallback: () => JsxElement2;
193
197
  }
194
198
  /**
195
199
  * Suspense component for async boundaries.
@@ -202,7 +206,7 @@ interface SuspenseProps {
202
206
  * to the nearest ErrorBoundary. If no ErrorBoundary exists, the error is surfaced
203
207
  * globally via queueMicrotask to avoid silent swallowing.
204
208
  */
205
- declare function Suspense(props: SuspenseProps): Node;
209
+ declare function Suspense(props: SuspenseProps): JsxElement2;
206
210
  declare const ANIMATION_DURATION: string;
207
211
  declare const ANIMATION_EASING: string;
208
212
  declare const fadeIn: string;
@@ -1395,7 +1399,7 @@ declare const OutletContext: Context<OutletContextValue>;
1395
1399
  * Must be called inside a layout component rendered by RouterView.
1396
1400
  * Reads from OutletContext to determine which child to render.
1397
1401
  */
1398
- declare function Outlet(): Node;
1402
+ declare function Outlet(): HTMLElement;
1399
1403
  declare const RouterContext: Context<Router>;
1400
1404
  declare function useRouter<T extends Record<string, RouteConfigLike> = RouteDefinitionMap>(): UnwrapSignals<Router<T>>;
1401
1405
  /**
package/dist/src/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  slideOutToTop,
22
22
  zoomIn,
23
23
  zoomOut
24
- } from "../shared/chunk-6jyt4ycw.js";
24
+ } from "../shared/chunk-yjs76c7v.js";
25
25
  import {
26
26
  Link,
27
27
  Outlet,
@@ -30,8 +30,8 @@ import {
30
30
  createLink,
31
31
  parseSearchParams,
32
32
  useSearchParams
33
- } from "../shared/chunk-b0qqqk03.js";
34
- import"../shared/chunk-1rxa2fz4.js";
33
+ } from "../shared/chunk-j5qtsm0b.js";
34
+ import"../shared/chunk-07bh4m1e.js";
35
35
  import {
36
36
  createRouter
37
37
  } from "../shared/chunk-fkbgbf3n.js";
@@ -57,7 +57,7 @@ import {
57
57
  queryMatch,
58
58
  registerRelationSchema,
59
59
  resetRelationSchemas_TEST_ONLY
60
- } from "../shared/chunk-4mtn7af6.js";
60
+ } from "../shared/chunk-fs3eec4b.js";
61
61
  import"../shared/chunk-jrtrk5z4.js";
62
62
  import {
63
63
  ThemeProvider,
@@ -74,16 +74,7 @@ import {
74
74
  resolveChildren,
75
75
  s,
76
76
  variants
77
- } from "../shared/chunk-dhehvmj0.js";
78
- import {
79
- RouterContext,
80
- useParams,
81
- useRouter
82
- } from "../shared/chunk-mtsvrj9e.js";
83
- import"../shared/chunk-pnv25zep.js";
84
- import {
85
- isBrowser
86
- } from "../shared/chunk-14eqne2a.js";
77
+ } from "../shared/chunk-c3r237f0.js";
87
78
  import {
88
79
  __append,
89
80
  __element,
@@ -96,7 +87,7 @@ import {
96
87
  exitChildren,
97
88
  getIsHydrating,
98
89
  startHydration
99
- } from "../shared/chunk-vndfjfdy.js";
90
+ } from "../shared/chunk-mgfrrrjq.js";
100
91
  import"../shared/chunk-prj7nm08.js";
101
92
  import {
102
93
  RENDER_NODE_BRAND,
@@ -105,6 +96,14 @@ import {
105
96
  isRenderNode,
106
97
  setAdapter
107
98
  } from "../shared/chunk-afawz764.js";
99
+ import {
100
+ RouterContext,
101
+ useParams,
102
+ useRouter
103
+ } from "../shared/chunk-mtsvrj9e.js";
104
+ import {
105
+ isBrowser
106
+ } from "../shared/chunk-14eqne2a.js";
108
107
  import {
109
108
  DisposalScopeError,
110
109
  _tryOnCleanup,
@@ -969,7 +969,34 @@ interface SSRRenderContext {
969
969
  * Used by the build pipeline to discover which routes to pre-render.
970
970
  */
971
971
  discoveredRoutes?: string[];
972
+ /**
973
+ * Auth state resolved by the server (e.g. from session cookie).
974
+ * Set by ssrRenderToString() before Pass 1 so AuthProvider can
975
+ * hydrate status/user synchronously during SSR.
976
+ */
977
+ ssrAuth?: SSRAuth;
978
+ /**
979
+ * Written by ProtectedRoute during Pass 1 when the user is not
980
+ * authenticated. Signals ssrRenderToString() to skip Pass 2 and
981
+ * return a redirect response instead.
982
+ */
983
+ ssrRedirect?: {
984
+ to: string;
985
+ };
972
986
  }
987
+ /** Auth state injected into SSRRenderContext by the server. */
988
+ type SSRAuth = {
989
+ status: "authenticated";
990
+ user: {
991
+ id: string;
992
+ email: string;
993
+ role: string;
994
+ [key: string]: unknown;
995
+ };
996
+ expiresAt: number;
997
+ } | {
998
+ status: "unauthenticated";
999
+ };
973
1000
  type SSRContextResolver = () => SSRRenderContext | undefined;
974
1001
  declare function registerSSRResolver(resolver: SSRContextResolver | null): void;
975
1002
  declare function getSSRContext(): SSRRenderContext | undefined;
@@ -983,4 +1010,4 @@ declare function getSSRContext(): SSRRenderContext | undefined;
983
1010
  * clears during HMR module re-evaluation.
984
1011
  */
985
1012
  declare function hasSSRResolver(): boolean;
986
- export { stopSignalCollection, startSignalCollection, setContextScope, setAdapter, runCleanups, resolveComponent, removeNode, registerSSRResolver, pushScope, popScope, onCleanup, onAnimationsComplete, matchRoute, matchPath, lifecycleEffect, isRenderNode, isBrowser, insertBefore, hasSSRResolver, getSSRContext, getContextScope, getAdapter, executeLoaders, domEffect, deserializeProps, deriveKey, createDOMAdapter, compileTheme, clearChildren, _tryOnCleanup, __text, __staticText, __show, __on, __list, __insert, __exitChildren, __enterChildren, __element, __conditional, __classList, __child, __attr, __append, SSRRenderContext, SSRQueryEntry, SPACING_SCALE, SIZE_KEYWORDS, SHADOW_SCALE, RenderText, RenderNode, RenderElement, RenderAdapter, RENDER_NODE_BRAND, RADIUS_SCALE, QueryEnvelopeStore, PropertyMapping, PSEUDO_PREFIXES, PSEUDO_MAP, PROPERTY_MAP, MemoryCache, MatchResult, LINE_HEIGHT_SCALE, KEYWORD_MAP, HEIGHT_AXIS_PROPERTIES, FONT_WEIGHT_SCALE, FONT_SIZE_SCALE, EntityStore, DISPLAY_MAP, CSS_COLOR_KEYWORDS, CSSDeclarationEntry, CONTENT_MAP, COLOR_NAMESPACES, ALIGNMENT_MAP };
1013
+ export { stopSignalCollection, startSignalCollection, setContextScope, setAdapter, runCleanups, resolveComponent, removeNode, registerSSRResolver, pushScope, popScope, onCleanup, onAnimationsComplete, matchRoute, matchPath, lifecycleEffect, isRenderNode, isBrowser, insertBefore, hasSSRResolver, getSSRContext, getContextScope, getAdapter, executeLoaders, domEffect, deserializeProps, deriveKey, createDOMAdapter, compileTheme, clearChildren, _tryOnCleanup, __text, __staticText, __show, __on, __list, __insert, __exitChildren, __enterChildren, __element, __conditional, __classList, __child, __attr, __append, SSRRenderContext, SSRQueryEntry, SSRAuth, SPACING_SCALE, SIZE_KEYWORDS, SHADOW_SCALE, RenderText, RenderNode, RenderElement, RenderAdapter, RENDER_NODE_BRAND, RADIUS_SCALE, QueryEnvelopeStore, PropertyMapping, PSEUDO_PREFIXES, PSEUDO_MAP, PROPERTY_MAP, MemoryCache, MatchResult, LINE_HEIGHT_SCALE, KEYWORD_MAP, HEIGHT_AXIS_PROPERTIES, FONT_WEIGHT_SCALE, FONT_SIZE_SCALE, EntityStore, DISPLAY_MAP, CSS_COLOR_KEYWORDS, CSSDeclarationEntry, CONTENT_MAP, COLOR_NAMESPACES, ALIGNMENT_MAP };
@@ -2,12 +2,13 @@ import {
2
2
  deserializeProps,
3
3
  onAnimationsComplete,
4
4
  resolveComponent
5
- } from "../shared/chunk-6jyt4ycw.js";
5
+ } from "../shared/chunk-yjs76c7v.js";
6
6
  import {
7
7
  __attr,
8
8
  __classList,
9
+ __on,
9
10
  __show
10
- } from "../shared/chunk-1rxa2fz4.js";
11
+ } from "../shared/chunk-07bh4m1e.js";
11
12
  import {
12
13
  executeLoaders,
13
14
  matchPath,
@@ -18,7 +19,7 @@ import {
18
19
  MemoryCache,
19
20
  QueryEnvelopeStore,
20
21
  deriveKey
21
- } from "../shared/chunk-4mtn7af6.js";
22
+ } from "../shared/chunk-fs3eec4b.js";
22
23
  import"../shared/chunk-jrtrk5z4.js";
23
24
  import {
24
25
  ALIGNMENT_MAP,
@@ -39,13 +40,7 @@ import {
39
40
  SIZE_KEYWORDS,
40
41
  SPACING_SCALE,
41
42
  compileTheme
42
- } from "../shared/chunk-dhehvmj0.js";
43
- import {
44
- __on
45
- } from "../shared/chunk-pnv25zep.js";
46
- import {
47
- isBrowser
48
- } from "../shared/chunk-14eqne2a.js";
43
+ } from "../shared/chunk-c3r237f0.js";
49
44
  import {
50
45
  __append,
51
46
  __child,
@@ -58,7 +53,7 @@ import {
58
53
  claimComment,
59
54
  claimText,
60
55
  getIsHydrating
61
- } from "../shared/chunk-vndfjfdy.js";
56
+ } from "../shared/chunk-mgfrrrjq.js";
62
57
  import"../shared/chunk-prj7nm08.js";
63
58
  import {
64
59
  RENDER_NODE_BRAND,
@@ -67,6 +62,9 @@ import {
67
62
  isRenderNode,
68
63
  setAdapter
69
64
  } from "../shared/chunk-afawz764.js";
65
+ import {
66
+ isBrowser
67
+ } from "../shared/chunk-14eqne2a.js";
70
68
  import {
71
69
  _tryOnCleanup,
72
70
  domEffect,
@@ -1,3 +1,7 @@
1
+ /** A ref container for DOM element access. */
2
+ interface Ref<T> {
3
+ current: T | undefined;
4
+ }
1
5
  /**
2
6
  * JSX namespace - required for TypeScript's react-jsx mode
3
7
  * to understand intrinsic element types and component types.
@@ -11,6 +15,7 @@ declare namespace JSX {
11
15
  }
12
16
  interface IntrinsicAttributes {
13
17
  key?: string | number;
18
+ ref?: Ref<unknown> | ((el: Element) => void);
14
19
  }
15
20
  interface IntrinsicElements {
16
21
  [key: string]: HTMLAttributes | undefined;
@@ -26,7 +26,7 @@ function jsxImpl(tag, props) {
26
26
  if (typeof tag === "function") {
27
27
  return tag(props || {});
28
28
  }
29
- const { children, ...attrs } = props || {};
29
+ const { children, ref: refProp, ...attrs } = props || {};
30
30
  const svg = isSVGTag(tag);
31
31
  const element = svg ? document.createElementNS(SVG_NS, tag) : document.createElement(tag);
32
32
  for (const [key, value] of Object.entries(attrs)) {
@@ -45,6 +45,13 @@ function jsxImpl(tag, props) {
45
45
  }
46
46
  }
47
47
  applyChildren(element, children);
48
+ if (refProp != null) {
49
+ if (typeof refProp === "function") {
50
+ refProp(element);
51
+ } else if (typeof refProp === "object" && "current" in refProp) {
52
+ refProp.current = element;
53
+ }
54
+ }
48
55
  return element;
49
56
  }
50
57
  function jsx(tag, props) {
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  query,
3
3
  queryMatch
4
- } from "../../shared/chunk-4mtn7af6.js";
4
+ } from "../../shared/chunk-fs3eec4b.js";
5
5
  import"../../shared/chunk-jrtrk5z4.js";
6
- import"../../shared/chunk-14eqne2a.js";
7
6
  import"../../shared/chunk-afawz764.js";
7
+ import"../../shared/chunk-14eqne2a.js";
8
8
  import"../../shared/chunk-4fwcwxn6.js";
9
9
 
10
10
  // src/query/public.ts
@@ -388,7 +388,7 @@ declare const OutletContext: Context<OutletContextValue>;
388
388
  * Must be called inside a layout component rendered by RouterView.
389
389
  * Reads from OutletContext to determine which child to render.
390
390
  */
391
- declare function Outlet(): Node;
391
+ declare function Outlet(): HTMLElement;
392
392
  declare const RouterContext: Context<Router>;
393
393
  declare function useRouter<T extends Record<string, RouteConfigLike> = RouteDefinitionMap>(): UnwrapSignals<Router<T>>;
394
394
  /**
@@ -6,8 +6,8 @@ import {
6
6
  createLink,
7
7
  parseSearchParams,
8
8
  useSearchParams
9
- } from "../../shared/chunk-b0qqqk03.js";
10
- import"../../shared/chunk-1rxa2fz4.js";
9
+ } from "../../shared/chunk-j5qtsm0b.js";
10
+ import"../../shared/chunk-07bh4m1e.js";
11
11
  import {
12
12
  createRouter
13
13
  } from "../../shared/chunk-fkbgbf3n.js";
@@ -15,16 +15,15 @@ import {
15
15
  defineRoutes
16
16
  } from "../../shared/chunk-6wd36w21.js";
17
17
  import"../../shared/chunk-jrtrk5z4.js";
18
+ import"../../shared/chunk-mgfrrrjq.js";
19
+ import"../../shared/chunk-prj7nm08.js";
20
+ import"../../shared/chunk-afawz764.js";
18
21
  import {
19
22
  RouterContext,
20
23
  useParams,
21
24
  useRouter
22
25
  } from "../../shared/chunk-mtsvrj9e.js";
23
- import"../../shared/chunk-pnv25zep.js";
24
26
  import"../../shared/chunk-14eqne2a.js";
25
- import"../../shared/chunk-vndfjfdy.js";
26
- import"../../shared/chunk-prj7nm08.js";
27
- import"../../shared/chunk-afawz764.js";
28
27
  import"../../shared/chunk-4fwcwxn6.js";
29
28
  export {
30
29
  useSearchParams,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/ui",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz UI framework — signals, components, JSX runtime",
@@ -69,11 +69,11 @@
69
69
  "typecheck": "tsc --noEmit"
70
70
  },
71
71
  "dependencies": {
72
- "@vertz/fetch": "^0.2.15"
72
+ "@vertz/fetch": "^0.2.17"
73
73
  },
74
74
  "devDependencies": {
75
75
  "@happy-dom/global-registrator": "^20.7.0",
76
- "@vertz/schema": "^0.2.15",
76
+ "@vertz/schema": "^0.2.17",
77
77
  "bunup": "^0.16.31",
78
78
  "happy-dom": "^20.7.0",
79
79
  "typescript": "^5.7.0"
@@ -1,7 +0,0 @@
1
- // src/dom/events.ts
2
- function __on(el, event, handler) {
3
- el.addEventListener(event, handler);
4
- return () => el.removeEventListener(event, handler);
5
- }
6
-
7
- export { __on };
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  isNavPrefetchActive
3
3
  } from "./chunk-jrtrk5z4.js";
4
- import {
5
- isBrowser
6
- } from "./chunk-14eqne2a.js";
7
4
  import {
8
5
  getAdapter,
9
6
  isRenderNode
10
7
  } from "./chunk-afawz764.js";
8
+ import {
9
+ isBrowser
10
+ } from "./chunk-14eqne2a.js";
11
11
  import {
12
12
  _tryOnCleanup,
13
13
  batch,