@vertz/ui 0.2.28 → 0.2.29

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeLoaders,
3
3
  matchRoute
4
- } from "./chunk-am9zaw4h.js";
4
+ } from "./chunk-ah86rm07.js";
5
5
  import {
6
6
  prefetchNavData
7
7
  } from "./chunk-jrtrk5z4.js";
@@ -13,6 +13,117 @@ import {
13
13
  signal
14
14
  } from "./chunk-ppr06jgn.js";
15
15
 
16
+ // src/router/reactive-search-params.ts
17
+ function shallowEqual(a, b) {
18
+ const keysA = Object.keys(a);
19
+ const keysB = Object.keys(b);
20
+ if (keysA.length !== keysB.length)
21
+ return false;
22
+ for (const key of keysA) {
23
+ if (a[key] !== b[key])
24
+ return false;
25
+ }
26
+ return true;
27
+ }
28
+ function buildMergedSearch(current, pending) {
29
+ const merged = { ...current };
30
+ for (const [key, value] of Object.entries(pending)) {
31
+ if (value === undefined || value === null) {
32
+ delete merged[key];
33
+ } else {
34
+ merged[key] = value;
35
+ }
36
+ }
37
+ return merged;
38
+ }
39
+ function currentPathname() {
40
+ return typeof window !== "undefined" ? window.location.pathname : "/";
41
+ }
42
+ function createReactiveSearchParams(rawSearchParamsSignal, navigateFn) {
43
+ let pending = null;
44
+ let capturedPathname = null;
45
+ function flush() {
46
+ if (!pending)
47
+ return;
48
+ const merged = buildMergedSearch(rawSearchParamsSignal.value, pending);
49
+ const pathname = capturedPathname ?? currentPathname();
50
+ pending = null;
51
+ capturedPathname = null;
52
+ if (shallowEqual(merged, rawSearchParamsSignal.value))
53
+ return;
54
+ navigateFn({
55
+ to: pathname,
56
+ search: merged,
57
+ replace: true
58
+ });
59
+ }
60
+ function navigateWithOptions(partial, options) {
61
+ pending = null;
62
+ capturedPathname = null;
63
+ const merged = buildMergedSearch(rawSearchParamsSignal.value, partial);
64
+ navigateFn({
65
+ to: currentPathname(),
66
+ search: merged,
67
+ replace: !options?.push
68
+ });
69
+ }
70
+ return new Proxy({}, {
71
+ get(_target, key) {
72
+ if (key === "navigate")
73
+ return navigateWithOptions;
74
+ if (typeof key === "symbol")
75
+ return;
76
+ if (pending && key in pending)
77
+ return pending[key];
78
+ return rawSearchParamsSignal.value[key];
79
+ },
80
+ set(_target, key, value) {
81
+ if (typeof key === "symbol")
82
+ return false;
83
+ if (!pending) {
84
+ pending = {};
85
+ capturedPathname = currentPathname();
86
+ queueMicrotask(flush);
87
+ }
88
+ pending[key] = value;
89
+ return true;
90
+ },
91
+ deleteProperty(_target, key) {
92
+ if (typeof key === "symbol")
93
+ return false;
94
+ if (!pending) {
95
+ pending = {};
96
+ capturedPathname = currentPathname();
97
+ queueMicrotask(flush);
98
+ }
99
+ pending[key] = undefined;
100
+ return true;
101
+ },
102
+ ownKeys() {
103
+ const current = pending ? buildMergedSearch(rawSearchParamsSignal.value, pending) : rawSearchParamsSignal.value;
104
+ return Object.keys(current);
105
+ },
106
+ getOwnPropertyDescriptor(_target, key) {
107
+ if (typeof key === "symbol")
108
+ return;
109
+ const val = pending && key in pending ? pending[key] : rawSearchParamsSignal.value[key];
110
+ if (val === undefined || val === null)
111
+ return;
112
+ return { configurable: true, enumerable: true, writable: true, value: val };
113
+ },
114
+ has(_target, key) {
115
+ if (typeof key === "symbol")
116
+ return false;
117
+ if (key === "navigate")
118
+ return true;
119
+ if (pending && key in pending) {
120
+ return pending[key] !== undefined && pending[key] !== null;
121
+ }
122
+ return key in rawSearchParamsSignal.value;
123
+ }
124
+ });
125
+ }
126
+
16
127
  // src/router/view-transitions.ts
17
128
  var transitionGen = 0;
18
129
  async function withViewTransition(update, config) {
@@ -124,7 +235,82 @@ function createRouter(routes, initialUrlOrOptions, maybeOptions) {
124
235
  };
125
236
  const ssrUrl = initialUrl ?? ssrCtx?.url ?? "/";
126
237
  const fallbackMatch = matchRoute(routes, ssrUrl);
238
+ const ssrSearchParamsSignal = {
239
+ get value() {
240
+ const ctx = getSSRContext();
241
+ if (ctx) {
242
+ const m = matchRoute(routes, ctx.url);
243
+ return m?.search ?? {};
244
+ }
245
+ return fallbackMatch?.search ?? {};
246
+ },
247
+ peek() {
248
+ const ctx = getSSRContext();
249
+ if (ctx) {
250
+ const m = matchRoute(routes, ctx.url);
251
+ return m?.search ?? {};
252
+ }
253
+ return fallbackMatch?.search ?? {};
254
+ },
255
+ notify() {}
256
+ };
257
+ const ssrReactiveSearchParams = new Proxy({}, {
258
+ get(_target, key) {
259
+ if (key === "navigate") {
260
+ return () => {
261
+ if (true) {
262
+ throw new Error("useSearchParams().navigate() is not supported during SSR. " + "Use schema defaults for initial values.");
263
+ }
264
+ };
265
+ }
266
+ if (typeof key === "symbol")
267
+ return;
268
+ const ctx = getSSRContext();
269
+ if (ctx) {
270
+ const m = matchRoute(routes, ctx.url);
271
+ return m?.search?.[key];
272
+ }
273
+ return fallbackMatch?.search?.[key];
274
+ },
275
+ set() {
276
+ if (true) {
277
+ throw new Error("useSearchParams() writes are not supported during SSR. " + "Use schema defaults for initial values.");
278
+ }
279
+ return true;
280
+ },
281
+ deleteProperty() {
282
+ if (true) {
283
+ throw new Error("useSearchParams() writes are not supported during SSR. " + "Use schema defaults for initial values.");
284
+ }
285
+ return true;
286
+ },
287
+ ownKeys() {
288
+ const ctx = getSSRContext();
289
+ const search = ctx ? matchRoute(routes, ctx.url)?.search ?? {} : fallbackMatch?.search ?? {};
290
+ return Object.keys(search);
291
+ },
292
+ getOwnPropertyDescriptor(_target, key) {
293
+ if (typeof key === "symbol")
294
+ return;
295
+ const ctx = getSSRContext();
296
+ const search = ctx ? matchRoute(routes, ctx.url)?.search ?? {} : fallbackMatch?.search ?? {};
297
+ const val = search[key];
298
+ if (val === undefined)
299
+ return;
300
+ return { configurable: true, enumerable: true, writable: true, value: val };
301
+ },
302
+ has(_target, key) {
303
+ if (typeof key === "symbol")
304
+ return false;
305
+ if (key === "navigate")
306
+ return true;
307
+ const ctx = getSSRContext();
308
+ const search = ctx ? matchRoute(routes, ctx.url)?.search ?? {} : fallbackMatch?.search ?? {};
309
+ return key in search;
310
+ }
311
+ });
127
312
  return {
313
+ _reactiveSearchParams: ssrReactiveSearchParams,
128
314
  current: {
129
315
  get value() {
130
316
  const ctx = getSSRContext();
@@ -142,25 +328,7 @@ function createRouter(routes, initialUrlOrOptions, maybeOptions) {
142
328
  },
143
329
  notify() {}
144
330
  },
145
- searchParams: {
146
- get value() {
147
- const ctx = getSSRContext();
148
- if (ctx) {
149
- const m = matchRoute(routes, ctx.url);
150
- return m?.search ?? {};
151
- }
152
- return fallbackMatch?.search ?? {};
153
- },
154
- peek() {
155
- const ctx = getSSRContext();
156
- if (ctx) {
157
- const m = matchRoute(routes, ctx.url);
158
- return m?.search ?? {};
159
- }
160
- return fallbackMatch?.search ?? {};
161
- },
162
- notify() {}
163
- },
331
+ searchParams: ssrSearchParamsSignal,
164
332
  loaderData: { value: [], peek: () => [], notify() {} },
165
333
  loaderError: { value: null, peek: () => null, notify() {} },
166
334
  navigate: () => Promise.resolve(),
@@ -367,7 +535,9 @@ function createRouter(routes, initialUrlOrOptions, maybeOptions) {
367
535
  activePrefetchUrl = null;
368
536
  }
369
537
  }
538
+ const _reactiveSearchParams = createReactiveSearchParams(_searchParams, navigate);
370
539
  return {
540
+ _reactiveSearchParams,
371
541
  current,
372
542
  dispose,
373
543
  loaderData,
@@ -76,8 +76,10 @@ function matchRoute(routes, url) {
76
76
  }
77
77
  }
78
78
  let search = {};
79
+ let foundSchema = false;
79
80
  for (const m of matched) {
80
81
  if (m.route.searchParams) {
82
+ foundSchema = true;
81
83
  const raw = {};
82
84
  for (const [key, value] of searchParams.entries()) {
83
85
  raw[key] = value;
@@ -89,6 +91,11 @@ function matchRoute(routes, url) {
89
91
  break;
90
92
  }
91
93
  }
94
+ if (!foundSchema) {
95
+ for (const [key, value] of searchParams.entries()) {
96
+ search[key] = value;
97
+ }
98
+ }
92
99
  return {
93
100
  matched,
94
101
  params: allParams,
@@ -551,8 +551,12 @@ function parseSearchParams(urlParams, schema) {
551
551
  }
552
552
  return raw;
553
553
  }
554
- function useSearchParams(searchSignal) {
555
- return searchSignal.value;
554
+ function useSearchParams() {
555
+ const router = useContext(RouterContext);
556
+ if (!router) {
557
+ throw new Error("useSearchParams() must be called within RouterContext.Provider");
558
+ }
559
+ return router._reactiveSearchParams;
556
560
  }
557
561
 
558
562
  export { getCurrentErrorHandler, ErrorBoundary, createLink, Link, OutletContext, Outlet, RouterView, parseSearchParams, useSearchParams };
@@ -1423,6 +1423,20 @@ type TypedRoutes<T extends Record<string, RouteConfigLike> = RouteDefinitionMap>
1423
1423
  * Usage: `useRouter<InferRouteMap<typeof routes>>()`
1424
1424
  */
1425
1425
  type InferRouteMap<T> = T extends TypedRoutes<infer R> ? R : T;
1426
+ /**
1427
+ * Extract the search params type from a route definition map for a given path.
1428
+ *
1429
+ * If the route at `TPath` has a `searchParams` schema, resolves to the schema's
1430
+ * output type `T`. Otherwise resolves to `Record<string, string>` (raw URL params).
1431
+ *
1432
+ * Parallel to `ExtractParams<TPath>` which extracts path params from the URL pattern.
1433
+ */
1434
+ type ExtractSearchParams<
1435
+ TPath extends string,
1436
+ TMap extends Record<string, RouteConfigLike> = RouteDefinitionMap
1437
+ > = TPath extends keyof TMap ? TMap[TPath] extends {
1438
+ searchParams: SearchParamSchema<infer T>;
1439
+ } ? T : Record<string, string> : Record<string, string>;
1426
1440
  /** Internal compiled route. */
1427
1441
  interface CompiledRoute {
1428
1442
  /** The original path pattern. */
@@ -1526,6 +1540,13 @@ declare function createLink(currentPath: ReadonlySignal<string>, navigate: (url:
1526
1540
  * Just use `<Link href="/about">About</Link>` inside a router-provided tree.
1527
1541
  */
1528
1542
  declare function Link({ href, children, activeClass, class: classProp, className }: LinkProps): HTMLAnchorElement;
1543
+ interface ReactiveSearchParams<T = Record<string, unknown>> {
1544
+ /** Batch-navigate with explicit push/replace option. Merges partial with current params. */
1545
+ navigate(partial: Partial<T>, options?: {
1546
+ push?: boolean;
1547
+ }): void;
1548
+ [key: string]: unknown;
1549
+ }
1529
1550
  type NavigateSearchValue = string | number | boolean | null | undefined;
1530
1551
  type NavigateSearch = string | URLSearchParams | Record<string, NavigateSearchValue | readonly NavigateSearchValue[]>;
1531
1552
  /** Options for router.navigate(). */
@@ -1600,6 +1621,8 @@ interface Router<T extends Record<string, RouteConfigLike> = RouteDefinitionMap>
1600
1621
  loaderError: Signal<Error | null>;
1601
1622
  /** Parsed search params from the current route (reactive signal). */
1602
1623
  searchParams: Signal<Record<string, unknown>>;
1624
+ /** @internal Reactive search params proxy — accessed via useSearchParams(). */
1625
+ _reactiveSearchParams: ReactiveSearchParams;
1603
1626
  /** Navigate to a route pattern, interpolating params and search into the final URL. */
1604
1627
  navigate<TPath extends RoutePattern<T>>(input: NavigateInput<TPath>): Promise<void>;
1605
1628
  /** Re-run all loaders for the current route. */
@@ -1688,13 +1711,27 @@ declare function RouterView({ router, fallback, errorFallback }: RouterViewProps
1688
1711
  */
1689
1712
  declare function parseSearchParams<T = Record<string, string>>(urlParams: URLSearchParams, schema?: SearchParamSchema<T>): T;
1690
1713
  /**
1691
- * Read the current search params from a reactive signal.
1692
- * Intended to be called inside a reactive context (effect/computed).
1714
+ * Read the current search params as a reactive, writable proxy.
1693
1715
  *
1694
- * @param searchSignal - Signal holding the current parsed search params
1695
- * @returns The current search params value
1716
+ * Overload 1: `useSearchParams<'/search'>()` infers search param types from
1717
+ * the route's `searchParams` schema via `ExtractSearchParams`. Requires codegen
1718
+ * augmentation or explicit `TMap` generic for full type inference.
1719
+ *
1720
+ * Overload 2: `useSearchParams<{ q: string; page: number }>()` — explicit type.
1721
+ *
1722
+ * Overload 3: `useSearchParams()` — no generic, returns `Record<string, string>`.
1723
+ *
1724
+ * Reads are reactive (trigger signal tracking), writes batch-navigate
1725
+ * to update the URL. Must be called within a `RouterContext.Provider`.
1726
+ */
1727
+ declare function useSearchParams<
1728
+ TPath extends string = string,
1729
+ TMap extends Record<string, RouteConfigLike> = RouteDefinitionMap
1730
+ >(): ReactiveSearchParams<ExtractSearchParams<TPath, TMap>>;
1731
+ /**
1732
+ * Read the current search params with an explicit type assertion.
1696
1733
  */
1697
- declare function useSearchParams<T>(searchSignal: ReadonlySignal<T>): T;
1734
+ declare function useSearchParams<T extends Record<string, unknown>>(): ReactiveSearchParams<T>;
1698
1735
  /**
1699
1736
  * Error thrown when `onCleanup()` is called outside a disposal scope.
1700
1737
  * Similar to React's invalid hook call error — fail-fast so developers
@@ -2074,4 +2111,4 @@ interface RegisterThemeInput {
2074
2111
  * ```
2075
2112
  */
2076
2113
  declare function registerTheme(resolved: RegisterThemeInput): void;
2077
- export { zoomOut, zoomIn, variants, validate, useSearchParams, useRouter, useParams, useDialogStack, useDialog, useContext, untrack, slideOutToTop, slideOutToRight, slideOutToLeft, slideOutToBottom, slideInFromTop, slideInFromRight, slideInFromLeft, slideInFromBottom, signal, setAdapter, s, resolveChildren, resetRelationSchemas_TEST_ONLY, resetInjectedStyles, registerTheme, registerRelationSchema, ref, query, parseSearchParams, palettes, onMount2 as onMount, onCleanup, onAnimationsComplete, mount, keyframes, isRenderNode, isQueryDescriptor, isBrowser, invalidateTenantQueries, invalidate, injectCSS, hydrateIslands, hydrate, globalCss, getRelationSchema, getQueryEnvelopeStore, getInjectedCSS, getEntityStore, getAdapter, formatRelativeTime, formDataToObject, form, font, fadeOut, fadeIn, defineTheme, defineRoutes, css, createTestStore, createRouter, createOptimisticHandler, createLink, createFieldState, createDialogStack, createDOMAdapter, createContext, configureImageOptimizer, computed, compileTheme, compileFonts, children, buildOptimizedUrl, batch, accordionUp, accordionDown, __staticText, __exitChildren, __enterChildren, __element, __append, VariantsConfig, VariantProps, VariantFunction, ValidationResult, UnwrapSignals, TypedRoutes, TypedRouter, ThemeProviderProps, ThemeProvider, ThemeInput, Theme, SuspenseProps, Suspense, StyleValue, StyleEntry, Signal, SerializedStore, SearchParamSchema, SdkMethodWithMeta, SdkMethod, RouterViewProps, RouterView, RouterOptions, RouterContext, Router, RoutePattern, RoutePaths, RouteMatch, RouteDefinitionMap, RouteConfig, RenderText, RenderNode, RenderElement, RenderAdapter, RelativeTimeProps, RelativeTime, RelationSchema, RelationFieldDef, RegisterThemeInput, Ref, ReadonlySignal, RENDER_NODE_BRAND, QueryResult, QueryOptions, QueryEnvelopeStore, QueryEnvelope, QueryDescriptor3 as QueryDescriptor, PresenceProps, Presence, PreloadItem, PathWithParams, ParamSchema, OutletContextValue, OutletContext, Outlet, NavigateOptions, NavigateInput, MountOptions, MountHandle, MergeSelectOptions, MatchedRoute, LoaderData, ListAnimationHooks, ListAnimationContext, LinkProps, LinkFactoryOptions, Link, IslandRegistry, IslandProps, Island, InferRouteMap, ImageProps, Image, GlobalCSSOutput, GlobalCSSInput, FormatRelativeTimeOptions, FormSchema, FormOptions, FormInstance, FormDataOptions, ForeignProps, Foreign, FontSrc, FontOptions, FontFallbackMetrics, FontDescriptor, FieldState, FieldSelectionTracker, FallbackFontName, ExtractParams, ErrorFallbackProps, ErrorBoundaryProps, ErrorBoundary, EntityStoreOptions, EntityStore, DisposeFn, DisposalScopeError, DialogStackProvider, DialogStackContext, DialogStack, DialogResult, DialogOpenOptions, DialogIdContext, DialogHandleContext, DialogHandle, DialogComponent, DefaultErrorFallback, DateInput, Context, ConfirmOptions, Computed, ComponentRegistry, ComponentLoader, ComponentFunction, CompiledTheme, CompiledRoute, CompiledFonts, CompileThemeOptions, CompileFontsOptions, ColorPalette, ChildrenAccessor, ChildValue, CacheStore, CSSOutput, CSSInput, ANIMATION_EASING, ANIMATION_DURATION };
2114
+ export { zoomOut, zoomIn, variants, validate, useSearchParams, useRouter, useParams, useDialogStack, useDialog, useContext, untrack, slideOutToTop, slideOutToRight, slideOutToLeft, slideOutToBottom, slideInFromTop, slideInFromRight, slideInFromLeft, slideInFromBottom, signal, setAdapter, s, resolveChildren, resetRelationSchemas_TEST_ONLY, resetInjectedStyles, registerTheme, registerRelationSchema, ref, query, parseSearchParams, palettes, onMount2 as onMount, onCleanup, onAnimationsComplete, mount, keyframes, isRenderNode, isQueryDescriptor, isBrowser, invalidateTenantQueries, invalidate, injectCSS, hydrateIslands, hydrate, globalCss, getRelationSchema, getQueryEnvelopeStore, getInjectedCSS, getEntityStore, getAdapter, formatRelativeTime, formDataToObject, form, font, fadeOut, fadeIn, defineTheme, defineRoutes, css, createTestStore, createRouter, createOptimisticHandler, createLink, createFieldState, createDialogStack, createDOMAdapter, createContext, configureImageOptimizer, computed, compileTheme, compileFonts, children, buildOptimizedUrl, batch, accordionUp, accordionDown, __staticText, __exitChildren, __enterChildren, __element, __append, VariantsConfig, VariantProps, VariantFunction, ValidationResult, UnwrapSignals, TypedRoutes, TypedRouter, ThemeProviderProps, ThemeProvider, ThemeInput, Theme, SuspenseProps, Suspense, StyleValue, StyleEntry, Signal, SerializedStore, SearchParamSchema, SdkMethodWithMeta, SdkMethod, RouterViewProps, RouterView, RouterOptions, RouterContext, Router, RoutePattern, RoutePaths, RouteMatch, RouteDefinitionMap, RouteConfig, RenderText, RenderNode, RenderElement, RenderAdapter, RelativeTimeProps, RelativeTime, RelationSchema, RelationFieldDef, RegisterThemeInput, Ref, ReadonlySignal, ReactiveSearchParams, RENDER_NODE_BRAND, QueryResult, QueryOptions, QueryEnvelopeStore, QueryEnvelope, QueryDescriptor3 as QueryDescriptor, PresenceProps, Presence, PreloadItem, PathWithParams, ParamSchema, OutletContextValue, OutletContext, Outlet, NavigateOptions, NavigateInput, MountOptions, MountHandle, MergeSelectOptions, MatchedRoute, LoaderData, ListAnimationHooks, ListAnimationContext, LinkProps, LinkFactoryOptions, Link, IslandRegistry, IslandProps, Island, InferRouteMap, ImageProps, Image, GlobalCSSOutput, GlobalCSSInput, FormatRelativeTimeOptions, FormSchema, FormOptions, FormInstance, FormDataOptions, ForeignProps, Foreign, FontSrc, FontOptions, FontFallbackMetrics, FontDescriptor, FieldState, FieldSelectionTracker, FallbackFontName, ExtractSearchParams, ExtractParams, ErrorFallbackProps, ErrorBoundaryProps, ErrorBoundary, EntityStoreOptions, EntityStore, DisposeFn, DisposalScopeError, DialogStackProvider, DialogStackContext, DialogStack, DialogResult, DialogOpenOptions, DialogIdContext, DialogHandleContext, DialogHandle, DialogComponent, DefaultErrorFallback, DateInput, Context, ConfirmOptions, Computed, ComponentRegistry, ComponentLoader, ComponentFunction, CompiledTheme, CompiledRoute, CompiledFonts, CompileThemeOptions, CompileFontsOptions, ColorPalette, ChildrenAccessor, ChildValue, CacheStore, CSSOutput, CSSInput, ANIMATION_EASING, ANIMATION_DURATION };
package/dist/src/index.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  getCurrentErrorHandler,
37
37
  parseSearchParams,
38
38
  useSearchParams
39
- } from "../shared/chunk-2krx4aqe.js";
39
+ } from "../shared/chunk-kabxty17.js";
40
40
  import {
41
41
  beginDeferringMounts,
42
42
  discardDeferredMounts,
@@ -45,10 +45,10 @@ import {
45
45
  } from "../shared/chunk-svvqjmyy.js";
46
46
  import {
47
47
  createRouter
48
- } from "../shared/chunk-e09mdqcx.js";
48
+ } from "../shared/chunk-4jfn3qhq.js";
49
49
  import {
50
50
  defineRoutes
51
- } from "../shared/chunk-am9zaw4h.js";
51
+ } from "../shared/chunk-ah86rm07.js";
52
52
  import {
53
53
  createFieldState,
54
54
  form,
@@ -14,7 +14,7 @@ import {
14
14
  executeLoaders,
15
15
  matchPath,
16
16
  matchRoute
17
- } from "../shared/chunk-am9zaw4h.js";
17
+ } from "../shared/chunk-ah86rm07.js";
18
18
  import {
19
19
  EntityStore,
20
20
  MemoryCache,
@@ -161,6 +161,20 @@ type TypedRoutes<T extends Record<string, RouteConfigLike> = RouteDefinitionMap>
161
161
  * Usage: `useRouter<InferRouteMap<typeof routes>>()`
162
162
  */
163
163
  type InferRouteMap<T> = T extends TypedRoutes<infer R> ? R : T;
164
+ /**
165
+ * Extract the search params type from a route definition map for a given path.
166
+ *
167
+ * If the route at `TPath` has a `searchParams` schema, resolves to the schema's
168
+ * output type `T`. Otherwise resolves to `Record<string, string>` (raw URL params).
169
+ *
170
+ * Parallel to `ExtractParams<TPath>` which extracts path params from the URL pattern.
171
+ */
172
+ type ExtractSearchParams<
173
+ TPath extends string,
174
+ TMap extends Record<string, RouteConfigLike> = RouteDefinitionMap
175
+ > = TPath extends keyof TMap ? TMap[TPath] extends {
176
+ searchParams: SearchParamSchema<infer T>;
177
+ } ? T : Record<string, string> : Record<string, string>;
164
178
  /** Internal compiled route. */
165
179
  interface CompiledRoute {
166
180
  /** The original path pattern. */
@@ -307,6 +321,13 @@ declare function createLink(currentPath: ReadonlySignal<string>, navigate: (url:
307
321
  * Just use `<Link href="/about">About</Link>` inside a router-provided tree.
308
322
  */
309
323
  declare function Link({ href, children, activeClass, class: classProp, className }: LinkProps): HTMLAnchorElement;
324
+ interface ReactiveSearchParams<T = Record<string, unknown>> {
325
+ /** Batch-navigate with explicit push/replace option. Merges partial with current params. */
326
+ navigate(partial: Partial<T>, options?: {
327
+ push?: boolean;
328
+ }): void;
329
+ [key: string]: unknown;
330
+ }
310
331
  type NavigateSearchValue = string | number | boolean | null | undefined;
311
332
  type NavigateSearch = string | URLSearchParams | Record<string, NavigateSearchValue | readonly NavigateSearchValue[]>;
312
333
  /** Options for router.navigate(). */
@@ -381,6 +402,8 @@ interface Router<T extends Record<string, RouteConfigLike> = RouteDefinitionMap>
381
402
  loaderError: Signal<Error | null>;
382
403
  /** Parsed search params from the current route (reactive signal). */
383
404
  searchParams: Signal<Record<string, unknown>>;
405
+ /** @internal Reactive search params proxy — accessed via useSearchParams(). */
406
+ _reactiveSearchParams: ReactiveSearchParams;
384
407
  /** Navigate to a route pattern, interpolating params and search into the final URL. */
385
408
  navigate<TPath extends RoutePattern<T>>(input: NavigateInput<TPath>): Promise<void>;
386
409
  /** Re-run all loaders for the current route. */
@@ -497,11 +520,25 @@ declare function RouterView({ router, fallback, errorFallback }: RouterViewProps
497
520
  */
498
521
  declare function parseSearchParams<T = Record<string, string>>(urlParams: URLSearchParams, schema?: SearchParamSchema<T>): T;
499
522
  /**
500
- * Read the current search params from a reactive signal.
501
- * Intended to be called inside a reactive context (effect/computed).
523
+ * Read the current search params as a reactive, writable proxy.
524
+ *
525
+ * Overload 1: `useSearchParams<'/search'>()` — infers search param types from
526
+ * the route's `searchParams` schema via `ExtractSearchParams`. Requires codegen
527
+ * augmentation or explicit `TMap` generic for full type inference.
502
528
  *
503
- * @param searchSignal - Signal holding the current parsed search params
504
- * @returns The current search params value
529
+ * Overload 2: `useSearchParams<{ q: string; page: number }>()` explicit type.
530
+ *
531
+ * Overload 3: `useSearchParams()` — no generic, returns `Record<string, string>`.
532
+ *
533
+ * Reads are reactive (trigger signal tracking), writes batch-navigate
534
+ * to update the URL. Must be called within a `RouterContext.Provider`.
535
+ */
536
+ declare function useSearchParams<
537
+ TPath extends string = string,
538
+ TMap extends Record<string, RouteConfigLike> = RouteDefinitionMap
539
+ >(): ReactiveSearchParams<ExtractSearchParams<TPath, TMap>>;
540
+ /**
541
+ * Read the current search params with an explicit type assertion.
505
542
  */
506
- declare function useSearchParams<T>(searchSignal: ReadonlySignal<T>): T;
507
- export { useSearchParams, useRouter, useParams, parseSearchParams, defineRoutes, createRouter, createLink, TypedRoutes, TypedRouter, SearchParamSchema, RouterViewProps, RouterView, RouterContext, Router, RoutePattern, RoutePaths, RouteMatch, RouteDefinitionMap, RouteConfig, PathWithParams, ParamSchema, OutletContextValue, OutletContext, Outlet, NavigateOptions, NavigateInput, MatchedRoute, LoaderData, LinkProps, Link, InferRouteMap, ExtractParams, CompiledRoute };
543
+ declare function useSearchParams<T extends Record<string, unknown>>(): ReactiveSearchParams<T>;
544
+ export { useSearchParams, useRouter, useParams, parseSearchParams, defineRoutes, createRouter, createLink, TypedRoutes, TypedRouter, SearchParamSchema, RouterViewProps, RouterView, RouterContext, Router, RoutePattern, RoutePaths, RouteMatch, RouteDefinitionMap, RouteConfig, ReactiveSearchParams, PathWithParams, ParamSchema, OutletContextValue, OutletContext, Outlet, NavigateOptions, NavigateInput, MatchedRoute, LoaderData, LinkProps, Link, InferRouteMap, ExtractSearchParams, ExtractParams, CompiledRoute };
@@ -6,14 +6,14 @@ import {
6
6
  createLink,
7
7
  parseSearchParams,
8
8
  useSearchParams
9
- } from "../../shared/chunk-2krx4aqe.js";
9
+ } from "../../shared/chunk-kabxty17.js";
10
10
  import"../../shared/chunk-svvqjmyy.js";
11
11
  import {
12
12
  createRouter
13
- } from "../../shared/chunk-e09mdqcx.js";
13
+ } from "../../shared/chunk-4jfn3qhq.js";
14
14
  import {
15
15
  defineRoutes
16
- } from "../../shared/chunk-am9zaw4h.js";
16
+ } from "../../shared/chunk-ah86rm07.js";
17
17
  import"../../shared/chunk-jrtrk5z4.js";
18
18
  import"../../shared/chunk-djvarb8r.js";
19
19
  import"../../shared/chunk-h1fsr8kv.js";
@@ -298,6 +298,13 @@ interface Signal<T> {
298
298
  /** Manually notify all subscribers (useful after mutating the value in place). */
299
299
  notify(): void;
300
300
  }
301
+ interface ReactiveSearchParams<T = Record<string, unknown>> {
302
+ /** Batch-navigate with explicit push/replace option. Merges partial with current params. */
303
+ navigate(partial: Partial<T>, options?: {
304
+ push?: boolean;
305
+ }): void;
306
+ [key: string]: unknown;
307
+ }
301
308
  type NavigateSearchValue = string | number | boolean | null | undefined;
302
309
  type NavigateSearch = string | URLSearchParams | Record<string, NavigateSearchValue | readonly NavigateSearchValue[]>;
303
310
  /** Options for router.navigate(). */
@@ -345,6 +352,8 @@ interface Router<T extends Record<string, RouteConfigLike> = RouteDefinitionMap>
345
352
  loaderError: Signal<Error | null>;
346
353
  /** Parsed search params from the current route (reactive signal). */
347
354
  searchParams: Signal<Record<string, unknown>>;
355
+ /** @internal Reactive search params proxy — accessed via useSearchParams(). */
356
+ _reactiveSearchParams: ReactiveSearchParams;
348
357
  /** Navigate to a route pattern, interpolating params and search into the final URL. */
349
358
  navigate<TPath extends RoutePattern<T>>(input: NavigateInput<TPath>): Promise<void>;
350
359
  /** Re-run all loaders for the current route. */
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  createRouter
3
- } from "../../shared/chunk-e09mdqcx.js";
3
+ } from "../../shared/chunk-4jfn3qhq.js";
4
4
  import {
5
5
  defineRoutes
6
- } from "../../shared/chunk-am9zaw4h.js";
6
+ } from "../../shared/chunk-ah86rm07.js";
7
7
  import"../../shared/chunk-jrtrk5z4.js";
8
8
  import"../../shared/chunk-xs5s8gqe.js";
9
9
  import"../../shared/chunk-ppr06jgn.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/ui",
3
- "version": "0.2.28",
3
+ "version": "0.2.29",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz UI framework — signals, components, JSX runtime",
@@ -74,11 +74,11 @@
74
74
  "typecheck": "tsc --noEmit"
75
75
  },
76
76
  "dependencies": {
77
- "@vertz/fetch": "^0.2.25"
77
+ "@vertz/fetch": "^0.2.28"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@happy-dom/global-registrator": "^20.7.0",
81
- "@vertz/schema": "^0.2.25",
81
+ "@vertz/schema": "^0.2.28",
82
82
  "bunup": "^0.16.31",
83
83
  "happy-dom": "^20.7.0",
84
84
  "typescript": "^5.7.0"
package/reactivity.json CHANGED
@@ -50,6 +50,12 @@
50
50
  "reactivity": {
51
51
  "type": "reactive-source"
52
52
  }
53
+ },
54
+ "useSearchParams": {
55
+ "kind": "function",
56
+ "reactivity": {
57
+ "type": "reactive-source"
58
+ }
53
59
  }
54
60
  },
55
61
  "filePath": "@vertz/ui",