@barefootjs/client 0.15.2 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- export { createSignal, createEffect, createDisposableEffect, createMemo, createRoot, onCleanup, onMount, untrack, batch, setProfilerSink, beginTurn, endTurn, __bfReportOutput, searchParams, __bfSetServerEnvReader, type Reactive, type Signal, type Memo, type CleanupFn, type EffectFn, type ProfilerEventSink, type SubscriberKind, } from '@barefootjs/client/reactive';
1
+ export { createSignal, createEffect, createDisposableEffect, createMemo, createRoot, onCleanup, onMount, untrack, batch, setProfilerSink, beginTurn, endTurn, __bfReportOutput, createSearchParams, type SearchParamsInit, __bfSetServerEnvReader, type Reactive, type Signal, type Memo, type CleanupFn, type EffectFn, type ProfilerEventSink, type SubscriberKind, } from '@barefootjs/client/reactive';
2
2
  export { createRecordingSink, type ProfilerEvent, type ProfilerEventType, type RecordingSink, } from './profiler-events.ts';
3
3
  export { splitProps } from './split-props.ts';
4
4
  export { __slot, type SlotMarker } from './slot.ts';
5
5
  export { forwardProps } from './forward-props.ts';
6
6
  export { unwrap } from './unwrap.ts';
7
+ export { queryHref, type QueryParams, type QueryParamValue } from './query-href.ts';
7
8
  export { createContext, type Context } from './context.ts';
8
9
  export { useContext, provideContext, createPortal, isSSRPortal, findSiblingSlot, cleanupPortalPlaceholder, type Portal, type PortalChildren, type PortalOptions, type Renderable, } from './shims.ts';
9
10
  export { Async, Region, type AsyncProps, type RegionProps, } from './builtins.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,eAAe,EACf,SAAS,EACT,OAAO,EACP,gBAAgB,EAKhB,YAAY,EACZ,sBAAsB,EACtB,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,EACL,UAAU,EACV,cAAc,EACd,YAAY,EACZ,WAAW,EACX,eAAe,EACf,wBAAwB,EACxB,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAA;AAKnB,OAAO,EACL,KAAK,EACL,MAAM,EACN,KAAK,UAAU,EACf,KAAK,WAAW,GACjB,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,eAAe,EACf,SAAS,EACT,OAAO,EACP,gBAAgB,EAMhB,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,OAAO,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEnF,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,EACL,UAAU,EACV,cAAc,EACd,YAAY,EACZ,WAAW,EACX,eAAe,EACf,wBAAwB,EACxB,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAA;AAKnB,OAAO,EACL,KAAK,EACL,MAAM,EACN,KAAK,UAAU,EACf,KAAK,WAAW,GACjB,MAAM,eAAe,CAAA"}
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  beginTurn,
14
14
  endTurn,
15
15
  __bfReportOutput,
16
- searchParams,
16
+ createSearchParams,
17
17
  __bfSetServerEnvReader
18
18
  } from "@barefootjs/client/reactive";
19
19
 
@@ -146,6 +146,21 @@ function forwardProps(source, overrides, excludeKeys) {
146
146
  function unwrap(prop) {
147
147
  return typeof prop === "function" ? prop() : prop;
148
148
  }
149
+ // src/query-href.ts
150
+ function queryHref(base, params) {
151
+ const u = new URLSearchParams;
152
+ for (const [key, value] of Object.entries(params)) {
153
+ if (Array.isArray(value)) {
154
+ for (const member of value)
155
+ if (member)
156
+ u.append(key, member);
157
+ } else if (value) {
158
+ u.set(key, value);
159
+ }
160
+ }
161
+ const qs = u.toString();
162
+ return qs ? `${base}?${qs}` : base;
163
+ }
149
164
  // src/context.ts
150
165
  function createContext(defaultValue) {
151
166
  return {
@@ -194,7 +209,7 @@ export {
194
209
  untrack,
195
210
  splitProps,
196
211
  setProfilerSink,
197
- searchParams,
212
+ queryHref,
198
213
  provideContext,
199
214
  onMount,
200
215
  onCleanup,
@@ -203,6 +218,7 @@ export {
203
218
  findSiblingSlot,
204
219
  endTurn,
205
220
  createSignal,
221
+ createSearchParams,
206
222
  createRoot,
207
223
  createRecordingSink,
208
224
  createPortal,
@@ -0,0 +1,39 @@
1
+ /**
2
+ * `queryHref` — build a URL from a base path plus query params, omitting falsy
3
+ * values. The functional counterpart to `searchParams()` (the reactive *reader*):
4
+ * instead of imperatively mutating a `URLSearchParams`, pass a params object.
5
+ *
6
+ * ```tsx
7
+ * const href = queryHref(base, {
8
+ * sort: sort !== 'date' ? sort : undefined, // conditional include via the value
9
+ * tag, // included only when truthy
10
+ * })
11
+ * ```
12
+ *
13
+ * Each entry is included iff its value is a **non-empty string** — `''` /
14
+ * `undefined` / `null` are omitted — so a conditional include folds into the
15
+ * value as `cond ? value : undefined`. Returns the bare `base` when no params
16
+ * survive. Values are encoded with `URLSearchParams` (form-encoding, spaces →
17
+ * `+`).
18
+ *
19
+ * A value may also be a **`string[]`**, which appends one entry per non-empty
20
+ * member (`{ tag: ['a', 'b'] }` → `?tag=a&tag=b`), i.e. `URLSearchParams.append`
21
+ * rather than `set`. Empty / falsy members are skipped (same truthy-omit as a
22
+ * scalar), so an empty array — or one whose members are all empty — contributes
23
+ * nothing.
24
+ *
25
+ * Values are **strings** (`QueryParamValue`). Number / boolean aren't accepted:
26
+ * JS truthiness would omit `0` / `false`, which the SSR adapters' string guard
27
+ * can't model without per-value type info — so keeping values string-only
28
+ * guarantees the server-rendered URL matches this client output byte-for-byte.
29
+ * Stringify other types at the call site (`String(n)`), choosing the omit rule
30
+ * explicitly (`n > 0 ? String(n) : undefined`).
31
+ *
32
+ * This is a pure function with no reactivity. The SSR adapters lower a
33
+ * `queryHref(base, { … })` call to their query helper (go-template: `bf_query`),
34
+ * which is why the params object must be a plain object literal at the call site.
35
+ */
36
+ export type QueryParamValue = string | string[] | null | undefined;
37
+ export type QueryParams = Record<string, QueryParamValue>;
38
+ export declare function queryHref(base: string, params: QueryParams): string;
39
+ //# sourceMappingURL=query-href.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-href.d.ts","sourceRoot":"","sources":["../src/query-href.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,CAAA;AAClE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;AAEzD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CAWnE"}
@@ -235,23 +235,12 @@ export declare function createMemo<T>(fn: () => T, __bfId?: string): Memo<T>;
235
235
  * resolves per-request.
236
236
  */
237
237
  export declare function __bfSetServerEnvReader(reader: ((key: string) => string | undefined) | null): void;
238
- /**
239
- * Reactive read of the current query string as `URLSearchParams`.
240
- *
241
- * ```tsx
242
- * const sort = createMemo(() => searchParams().get('sort') ?? 'recent')
243
- * ```
244
- *
245
- * A same-route, query-only navigation (`/list?sort=price`) driven by
246
- * `@barefootjs/router` updates this signal and the URL **without a swap or
247
- * re-hydration**. On the server it reflects the current request's query.
248
- *
249
- * Reactivity is **router-driven**: the signal is seeded once on first read and
250
- * thereafter updated only through the `window.__bf_pushSearch` seam, which
251
- * `startRouter()` drives (including on `popstate`). Without the router running
252
- * — or after a non-router `history` change — the value is read-once; the seam
253
- * name is shared with `@barefootjs/router` via `BF_SEAM_PUSH_SEARCH`, so the
254
- * installer (here) and the pusher agree by construction.
255
- */
256
- export declare const searchParams: Reactive<() => URLSearchParams>;
238
+ /** Accepted inputs for `setSearchParams` — a raw query string (with or without
239
+ * a leading `?`), a `URLSearchParams`, or a plain record (array = multi-value,
240
+ * form-encoded like the client `queryHref`, cf. #2048). */
241
+ export type SearchParamsInit = string | URLSearchParams | Record<string, string | readonly string[]>;
242
+ export declare function createSearchParams(): readonly [
243
+ Reactive<() => URLSearchParams>,
244
+ (next: SearchParamsInit) => void
245
+ ];
257
246
  //# sourceMappingURL=reactive.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reactive.d.ts","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reactive.d.ts","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"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"}
package/dist/reactive.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // ../shared/src/markers.ts
2
2
  var BF_SEAM_PUSH_SEARCH = "__bf_pushSearch";
3
+ var BF_SEAM_NAV_SEARCH = "__bf_navSearch";
3
4
  // ../shared/src/dom-prop.ts
4
5
  var BOOLEAN_ATTRS = new Set([
5
6
  "checked",
@@ -374,7 +375,7 @@ function resolveServerEnv(key) {
374
375
  const seam = globalThis.__bf_serverEnvReader;
375
376
  return typeof seam === "function" ? seam(key) : undefined;
376
377
  }
377
- function createEnvSignal(key, readClient, parse, pushSeam) {
378
+ function createEnvSignal(key, readClient, parse, pushSeam, navSeam) {
378
379
  let getRaw = null;
379
380
  function ensureClientSignal() {
380
381
  if (!getRaw) {
@@ -387,22 +388,59 @@ function createEnvSignal(key, readClient, parse, pushSeam) {
387
388
  }
388
389
  return getRaw();
389
390
  }
390
- return () => {
391
+ const getter = () => {
391
392
  if (typeof window === "undefined") {
392
393
  return parse(resolveServerEnv(key) ?? "");
393
394
  }
394
395
  return parse(ensureClientSignal());
395
396
  };
397
+ const setter = (nextRaw) => {
398
+ if (typeof window === "undefined")
399
+ return;
400
+ const w = window;
401
+ const nav = w[navSeam];
402
+ if (typeof nav === "function") {
403
+ nav(nextRaw);
404
+ return;
405
+ }
406
+ window.location.search = nextRaw;
407
+ };
408
+ return [getter, setter];
409
+ }
410
+ function toSearchString(next) {
411
+ let usp;
412
+ if (next instanceof URLSearchParams) {
413
+ usp = next;
414
+ } else if (typeof next === "string") {
415
+ usp = new URLSearchParams(next.startsWith("?") ? next.slice(1) : next);
416
+ } else {
417
+ usp = new URLSearchParams;
418
+ for (const [k, v] of Object.entries(next)) {
419
+ if (Array.isArray(v)) {
420
+ for (const item of v)
421
+ usp.append(k, item);
422
+ } else {
423
+ usp.append(k, v);
424
+ }
425
+ }
426
+ }
427
+ const s = usp.toString();
428
+ return s ? `?${s}` : "";
429
+ }
430
+ var [searchParamsGetter, setSearchParamsRaw] = createEnvSignal("search", () => window.location.search, (raw) => new URLSearchParams(raw), BF_SEAM_PUSH_SEARCH, BF_SEAM_NAV_SEARCH);
431
+ var setSearchParams = (next) => setSearchParamsRaw(toSearchString(next));
432
+ var searchParamsTuple = [searchParamsGetter, setSearchParams];
433
+ function createSearchParams() {
434
+ return searchParamsTuple;
396
435
  }
397
- var searchParams = createEnvSignal("search", () => window.location.search, (raw) => new URLSearchParams(raw), BF_SEAM_PUSH_SEARCH);
398
436
  export {
399
437
  untrack,
400
438
  setProfilerSink,
401
- searchParams,
402
439
  onMount,
403
440
  onCleanup,
404
441
  endTurn,
405
442
  createSignal,
443
+ createSearchParams,
406
444
  createRoot,
407
445
  createMemo,
408
446
  createEffect,
@@ -1,9 +1,10 @@
1
- export { createSignal, createEffect, createDisposableEffect, createMemo, createRoot, onCleanup, onMount, untrack, batch, setProfilerSink, beginTurn, endTurn, __bfReportOutput, searchParams, __bfSetServerEnvReader, type Reactive, type Signal, type Memo, type CleanupFn, type EffectFn, type ProfilerEventSink, type SubscriberKind, } from '@barefootjs/client/reactive';
1
+ export { createSignal, createEffect, createDisposableEffect, createMemo, createRoot, onCleanup, onMount, untrack, batch, setProfilerSink, beginTurn, endTurn, __bfReportOutput, createSearchParams, type SearchParamsInit, __bfSetServerEnvReader, type Reactive, type Signal, type Memo, type CleanupFn, type EffectFn, type ProfilerEventSink, type SubscriberKind, } from '@barefootjs/client/reactive';
2
2
  export { createRecordingSink, type ProfilerEvent, type ProfilerEventType, type RecordingSink, } from '../profiler-events.ts';
3
3
  export { splitProps } from '../split-props.ts';
4
4
  export { __slot, type SlotMarker } from '../slot.ts';
5
5
  export { forwardProps } from '../forward-props.ts';
6
6
  export { unwrap } from '../unwrap.ts';
7
+ export { queryHref, type QueryParams, type QueryParamValue } from '../query-href.ts';
7
8
  export { createContext, useContext, provideContext, setCurrentScope, type Context, } from './context.ts';
8
9
  export { createPortal, isSSRPortal, findSiblingSlot, cleanupPortalPlaceholder, type Portal, type PortalOptions, type Renderable, type PortalChildren, } from './portal.ts';
9
10
  export { reconcileList, type RenderItemFn } from './list.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,7 @@ import {
13
13
  beginTurn,
14
14
  endTurn,
15
15
  __bfReportOutput as __bfReportOutput2,
16
- searchParams,
16
+ createSearchParams,
17
17
  __bfSetServerEnvReader
18
18
  } from "@barefootjs/client/reactive";
19
19
 
@@ -146,6 +146,21 @@ function forwardProps(source, overrides, excludeKeys) {
146
146
  function unwrap(prop) {
147
147
  return typeof prop === "function" ? prop() : prop;
148
148
  }
149
+ // src/query-href.ts
150
+ function queryHref(base, params) {
151
+ const u = new URLSearchParams;
152
+ for (const [key, value] of Object.entries(params)) {
153
+ if (Array.isArray(value)) {
154
+ for (const member of value)
155
+ if (member)
156
+ u.append(key, member);
157
+ } else if (value) {
158
+ u.set(key, value);
159
+ }
160
+ }
161
+ const qs = u.toString();
162
+ return qs ? `${base}?${qs}` : base;
163
+ }
149
164
  // ../shared/src/markers.ts
150
165
  var BF_SCOPE = "bf-s";
151
166
  var BF_SLOT = "bf";
@@ -2556,7 +2571,6 @@ export {
2556
2571
  setupStreaming,
2557
2572
  setProfilerSink,
2558
2573
  setCurrentScope,
2559
- searchParams,
2560
2574
  renderChild,
2561
2575
  render,
2562
2576
  rehydrateScope,
@@ -2565,6 +2579,7 @@ export {
2565
2579
  registerComponent,
2566
2580
  reconcileList,
2567
2581
  reconcileElements,
2582
+ queryHref,
2568
2583
  qsaItem,
2569
2584
  qsaChildScopes,
2570
2585
  qsaChildScope,
@@ -2599,6 +2614,7 @@ export {
2599
2614
  disposeScope,
2600
2615
  cssEscape,
2601
2616
  createSignal2 as createSignal,
2617
+ createSearchParams,
2602
2618
  createRoot3 as createRoot,
2603
2619
  createRecordingSink,
2604
2620
  createPortal,
@@ -31,6 +31,7 @@ var BF_SEAM_HYDRATE = "__bf_hydrate";
31
31
  var BF_SEAM_HYDRATE_WITHIN = "__bf_hydrate_within";
32
32
  var BF_SEAM_DISPOSE_WITHIN = "__bf_dispose_within";
33
33
  var BF_SEAM_PUSH_SEARCH = "__bf_pushSearch";
34
+ var BF_SEAM_NAV_SEARCH = "__bf_navSearch";
34
35
  // ../shared/src/dom-prop.ts
35
36
  var BOOLEAN_ATTRS = new Set([
36
37
  "checked",
@@ -473,7 +474,7 @@ function resolveServerEnv(key) {
473
474
  const seam = globalThis.__bf_serverEnvReader;
474
475
  return typeof seam === "function" ? seam(key) : undefined;
475
476
  }
476
- function createEnvSignal(key, readClient, parse, pushSeam) {
477
+ function createEnvSignal(key, readClient, parse, pushSeam, navSeam) {
477
478
  let getRaw = null;
478
479
  function ensureClientSignal() {
479
480
  if (!getRaw) {
@@ -486,14 +487,51 @@ function createEnvSignal(key, readClient, parse, pushSeam) {
486
487
  }
487
488
  return getRaw();
488
489
  }
489
- return () => {
490
+ const getter = () => {
490
491
  if (typeof window === "undefined") {
491
492
  return parse(resolveServerEnv(key) ?? "");
492
493
  }
493
494
  return parse(ensureClientSignal());
494
495
  };
496
+ const setter = (nextRaw) => {
497
+ if (typeof window === "undefined")
498
+ return;
499
+ const w = window;
500
+ const nav = w[navSeam];
501
+ if (typeof nav === "function") {
502
+ nav(nextRaw);
503
+ return;
504
+ }
505
+ window.location.search = nextRaw;
506
+ };
507
+ return [getter, setter];
508
+ }
509
+ function toSearchString(next) {
510
+ let usp;
511
+ if (next instanceof URLSearchParams) {
512
+ usp = next;
513
+ } else if (typeof next === "string") {
514
+ usp = new URLSearchParams(next.startsWith("?") ? next.slice(1) : next);
515
+ } else {
516
+ usp = new URLSearchParams;
517
+ for (const [k, v] of Object.entries(next)) {
518
+ if (Array.isArray(v)) {
519
+ for (const item of v)
520
+ usp.append(k, item);
521
+ } else {
522
+ usp.append(k, v);
523
+ }
524
+ }
525
+ }
526
+ const s = usp.toString();
527
+ return s ? `?${s}` : "";
528
+ }
529
+ var [searchParamsGetter, setSearchParamsRaw] = createEnvSignal("search", () => window.location.search, (raw) => new URLSearchParams(raw), BF_SEAM_PUSH_SEARCH, BF_SEAM_NAV_SEARCH);
530
+ var setSearchParams = (next) => setSearchParamsRaw(toSearchString(next));
531
+ var searchParamsTuple = [searchParamsGetter, setSearchParams];
532
+ function createSearchParams() {
533
+ return searchParamsTuple;
495
534
  }
496
- var searchParams = createEnvSignal("search", () => window.location.search, (raw) => new URLSearchParams(raw), BF_SEAM_PUSH_SEARCH);
497
535
  // src/profiler-events.ts
498
536
  function createRecordingSink() {
499
537
  const events = [];
@@ -623,6 +661,21 @@ function forwardProps(source, overrides, excludeKeys) {
623
661
  function unwrap(prop) {
624
662
  return typeof prop === "function" ? prop() : prop;
625
663
  }
664
+ // src/query-href.ts
665
+ function queryHref(base, params) {
666
+ const u = new URLSearchParams;
667
+ for (const [key, value] of Object.entries(params)) {
668
+ if (Array.isArray(value)) {
669
+ for (const member of value)
670
+ if (member)
671
+ u.append(key, member);
672
+ } else if (value) {
673
+ u.set(key, value);
674
+ }
675
+ }
676
+ const qs = u.toString();
677
+ return qs ? `${base}?${qs}` : base;
678
+ }
626
679
  // src/context.ts
627
680
  function createContext(defaultValue) {
628
681
  return {
@@ -2841,7 +2894,6 @@ export {
2841
2894
  setupStreaming,
2842
2895
  setProfilerSink,
2843
2896
  setCurrentScope,
2844
- searchParams,
2845
2897
  renderChild,
2846
2898
  render,
2847
2899
  rehydrateScope,
@@ -2850,6 +2902,7 @@ export {
2850
2902
  registerComponent,
2851
2903
  reconcileList,
2852
2904
  reconcileElements,
2905
+ queryHref,
2853
2906
  qsaItem,
2854
2907
  qsaChildScopes,
2855
2908
  qsaChildScope,
@@ -2884,6 +2937,7 @@ export {
2884
2937
  disposeScope,
2885
2938
  cssEscape,
2886
2939
  createSignal,
2940
+ createSearchParams,
2887
2941
  createRoot,
2888
2942
  createRecordingSink,
2889
2943
  createPortal,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/client",
3
- "version": "0.15.2",
3
+ "version": "0.17.0",
4
4
  "description": "BarefootJS client package: reactive primitives (SSR-safe) plus browser runtime under the `/runtime` subpath (compiler target)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "directory": "packages/client"
56
56
  },
57
57
  "dependencies": {
58
- "@barefootjs/shared": "0.15.2"
58
+ "@barefootjs/shared": "0.17.0"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@barefootjs/jsx": ">=0.2.0"
package/src/index.ts CHANGED
@@ -16,10 +16,12 @@ export {
16
16
  endTurn,
17
17
  __bfReportOutput,
18
18
  // Request-scoped reactive environment signals (spec/router.md "The wedge").
19
- // `searchParams` lives in the shared reactive module too, so this entry and
20
- // the `/runtime` entry resolve to ONE signal instance. `createEnvSignal` stays
21
- // internal; `__bfSetServerEnvReader` is the keyed adapter/host hook for SSR.
22
- searchParams,
19
+ // `createSearchParams` lives in the shared reactive module too, so this entry
20
+ // and the `/runtime` entry resolve to ONE signal instance. `createEnvSignal`
21
+ // stays internal; `__bfSetServerEnvReader` is the keyed adapter/host hook for
22
+ // SSR.
23
+ createSearchParams,
24
+ type SearchParamsInit,
23
25
  __bfSetServerEnvReader,
24
26
  type Reactive,
25
27
  type Signal,
@@ -45,6 +47,8 @@ export { forwardProps } from './forward-props.ts'
45
47
 
46
48
  export { unwrap } from './unwrap.ts'
47
49
 
50
+ export { queryHref, type QueryParams, type QueryParamValue } from './query-href.ts'
51
+
48
52
  export { createContext, type Context } from './context.ts'
49
53
 
50
54
  export {
@@ -0,0 +1,50 @@
1
+ /**
2
+ * `queryHref` — build a URL from a base path plus query params, omitting falsy
3
+ * values. The functional counterpart to `searchParams()` (the reactive *reader*):
4
+ * instead of imperatively mutating a `URLSearchParams`, pass a params object.
5
+ *
6
+ * ```tsx
7
+ * const href = queryHref(base, {
8
+ * sort: sort !== 'date' ? sort : undefined, // conditional include via the value
9
+ * tag, // included only when truthy
10
+ * })
11
+ * ```
12
+ *
13
+ * Each entry is included iff its value is a **non-empty string** — `''` /
14
+ * `undefined` / `null` are omitted — so a conditional include folds into the
15
+ * value as `cond ? value : undefined`. Returns the bare `base` when no params
16
+ * survive. Values are encoded with `URLSearchParams` (form-encoding, spaces →
17
+ * `+`).
18
+ *
19
+ * A value may also be a **`string[]`**, which appends one entry per non-empty
20
+ * member (`{ tag: ['a', 'b'] }` → `?tag=a&tag=b`), i.e. `URLSearchParams.append`
21
+ * rather than `set`. Empty / falsy members are skipped (same truthy-omit as a
22
+ * scalar), so an empty array — or one whose members are all empty — contributes
23
+ * nothing.
24
+ *
25
+ * Values are **strings** (`QueryParamValue`). Number / boolean aren't accepted:
26
+ * JS truthiness would omit `0` / `false`, which the SSR adapters' string guard
27
+ * can't model without per-value type info — so keeping values string-only
28
+ * guarantees the server-rendered URL matches this client output byte-for-byte.
29
+ * Stringify other types at the call site (`String(n)`), choosing the omit rule
30
+ * explicitly (`n > 0 ? String(n) : undefined`).
31
+ *
32
+ * This is a pure function with no reactivity. The SSR adapters lower a
33
+ * `queryHref(base, { … })` call to their query helper (go-template: `bf_query`),
34
+ * which is why the params object must be a plain object literal at the call site.
35
+ */
36
+ export type QueryParamValue = string | string[] | null | undefined
37
+ export type QueryParams = Record<string, QueryParamValue>
38
+
39
+ export function queryHref(base: string, params: QueryParams): string {
40
+ const u = new URLSearchParams()
41
+ for (const [key, value] of Object.entries(params)) {
42
+ if (Array.isArray(value)) {
43
+ for (const member of value) if (member) u.append(key, member)
44
+ } else if (value) {
45
+ u.set(key, value)
46
+ }
47
+ }
48
+ const qs = u.toString()
49
+ return qs ? `${base}?${qs}` : base
50
+ }
package/src/reactive.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * Inspired by SolidJS signals.
6
6
  */
7
7
 
8
- import { BF_SEAM_PUSH_SEARCH } from '@barefootjs/shared'
8
+ import { BF_SEAM_NAV_SEARCH, BF_SEAM_PUSH_SEARCH } from '@barefootjs/shared'
9
9
 
10
10
  /**
11
11
  * Phantom brand for compile-time reactivity detection.
@@ -665,15 +665,22 @@ function resolveServerEnv(key: string): string | undefined {
665
665
 
666
666
  /**
667
667
  * Build a request-scoped reactive environment signal, keyed by `key` — the env
668
- * id the SSR reader resolves (`'search'`, …). Internal: only concrete instances
669
- * (`searchParams`, …) are exported.
668
+ * id the SSR reader resolves (`'search'`, …) as a `createSignal`-shaped
669
+ * `[getter, setter]` tuple. Internal: only concrete factories
670
+ * (`createSearchParams`, …) are exported.
671
+ *
672
+ * The tuple shape is deliberate (#2057): the compiler recognises the getter
673
+ * *structurally* — the same path as `createSignal` — so it needs no env-signal
674
+ * name allow-list, and the getter is pure-within-render for the block fold. The
675
+ * setter is the single imperative navigation path.
670
676
  */
671
677
  function createEnvSignal<T>(
672
678
  key: string,
673
679
  readClient: () => string,
674
680
  parse: (raw: string) => T,
675
681
  pushSeam: string,
676
- ): Reactive<() => T> {
682
+ navSeam: string,
683
+ ): readonly [Reactive<() => T>, (nextRaw: string) => void] {
677
684
  let getRaw: (() => string) | null = null
678
685
 
679
686
  function ensureClientSignal(): string {
@@ -693,7 +700,7 @@ function createEnvSignal<T>(
693
700
  return getRaw()
694
701
  }
695
702
 
696
- return (() => {
703
+ const getter = (() => {
697
704
  if (typeof window === 'undefined') {
698
705
  // SSR: resolve per-call inside the host's request context. Never cache a
699
706
  // module-level signal — it would be a process-wide global that races.
@@ -701,29 +708,100 @@ function createEnvSignal<T>(
701
708
  }
702
709
  return parse(ensureClientSignal())
703
710
  }) as Reactive<() => T>
711
+
712
+ // Imperative navigation. The setter writes the new raw value through the
713
+ // router's nav seam (soft, same-route query navigation) when a router has
714
+ // installed it; otherwise it hard-navigates — "never worse than an MPA" — so
715
+ // the write is correct standalone. The getter is then updated by the router
716
+ // through `pushSeam`, keeping read reactivity in one place. On the server a
717
+ // setter call is a no-op (there is nothing to navigate).
718
+ const setter = (nextRaw: string) => {
719
+ if (typeof window === 'undefined') return
720
+ const w = window as unknown as Record<string, ((next: string) => void) | undefined>
721
+ const nav = w[navSeam]
722
+ if (typeof nav === 'function') {
723
+ nav(nextRaw)
724
+ return
725
+ }
726
+ window.location.search = nextRaw
727
+ }
728
+
729
+ return [getter, setter] as const
704
730
  }
705
731
 
732
+ /** Accepted inputs for `setSearchParams` — a raw query string (with or without
733
+ * a leading `?`), a `URLSearchParams`, or a plain record (array = multi-value,
734
+ * form-encoded like the client `queryHref`, cf. #2048). */
735
+ export type SearchParamsInit =
736
+ | string
737
+ | URLSearchParams
738
+ | Record<string, string | readonly string[]>
739
+
740
+ function toSearchString(next: SearchParamsInit): string {
741
+ let usp: URLSearchParams
742
+ if (next instanceof URLSearchParams) {
743
+ usp = next
744
+ } else if (typeof next === 'string') {
745
+ usp = new URLSearchParams(next.startsWith('?') ? next.slice(1) : next)
746
+ } else {
747
+ usp = new URLSearchParams()
748
+ for (const [k, v] of Object.entries(next)) {
749
+ if (Array.isArray(v)) {
750
+ for (const item of v) usp.append(k, item)
751
+ } else {
752
+ usp.append(k, v as string)
753
+ }
754
+ }
755
+ }
756
+ const s = usp.toString()
757
+ return s ? `?${s}` : ''
758
+ }
759
+
760
+ const [searchParamsGetter, setSearchParamsRaw] = createEnvSignal(
761
+ 'search',
762
+ () => window.location.search,
763
+ (raw) => new URLSearchParams(raw),
764
+ BF_SEAM_PUSH_SEARCH,
765
+ BF_SEAM_NAV_SEARCH,
766
+ )
767
+
706
768
  /**
707
- * Reactive read of the current query string as `URLSearchParams`.
769
+ * `createSearchParams()` the request-scoped query-string env signal, returned
770
+ * as a `createSignal`-shaped `[getter, setter]` tuple (#2057):
708
771
  *
709
772
  * ```tsx
773
+ * const [searchParams, setSearchParams] = createSearchParams()
710
774
  * const sort = createMemo(() => searchParams().get('sort') ?? 'recent')
775
+ * // …
776
+ * setSearchParams({ sort: 'price' }) // imperative, same-route navigation
711
777
  * ```
712
778
  *
713
- * A same-route, query-only navigation (`/list?sort=price`) driven by
779
+ * **Read** (`searchParams()`): the current query as `URLSearchParams`. A
780
+ * same-route, query-only navigation (`/list?sort=price`) driven by
714
781
  * `@barefootjs/router` updates this signal and the URL **without a swap or
715
782
  * re-hydration**. On the server it reflects the current request's query.
716
- *
717
783
  * Reactivity is **router-driven**: the signal is seeded once on first read and
718
784
  * thereafter updated only through the `window.__bf_pushSearch` seam, which
719
- * `startRouter()` drives (including on `popstate`). Without the router running
720
- * — or after a non-router `history` change — the value is read-once; the seam
721
- * name is shared with `@barefootjs/router` via `BF_SEAM_PUSH_SEARCH`, so the
722
- * installer (here) and the pusher agree by construction.
785
+ * `startRouter()` drives (including on `popstate`).
786
+ *
787
+ * **Write** (`setSearchParams(next)`): the single imperative navigation path
788
+ * a soft same-route navigation when a router is running, a hard navigation
789
+ * otherwise. This replaces mutating a live `URLSearchParams` reader (which only
790
+ * ever changed a throwaway copy), so there is exactly one way to change the
791
+ * query.
792
+ *
793
+ * Every call returns the same request-scoped getter/setter — there is one query
794
+ * per document, so the tuple is a stable view, not per-call state.
723
795
  */
724
- export const searchParams = createEnvSignal(
725
- 'search',
726
- () => window.location.search,
727
- (raw) => new URLSearchParams(raw),
728
- BF_SEAM_PUSH_SEARCH,
729
- )
796
+ const setSearchParams = (next: SearchParamsInit): void => setSearchParamsRaw(toSearchString(next))
797
+ const searchParamsTuple: readonly [
798
+ Reactive<() => URLSearchParams>,
799
+ (next: SearchParamsInit) => void,
800
+ ] = [searchParamsGetter, setSearchParams]
801
+
802
+ export function createSearchParams(): readonly [
803
+ Reactive<() => URLSearchParams>,
804
+ (next: SearchParamsInit) => void,
805
+ ] {
806
+ return searchParamsTuple
807
+ }
@@ -23,11 +23,13 @@ export {
23
23
  endTurn,
24
24
  __bfReportOutput,
25
25
  // Request-scoped env signal (router v0.5). The compiler emits island client JS
26
- // that imports `searchParams` from `@barefootjs/client/runtime`; re-exporting
27
- // it from the shared reactive module (same as the signal primitives above)
28
- // means this entry and the main `@barefootjs/client` entry resolve to ONE
29
- // signal instance — no second copy to disconnect from router pushes.
30
- searchParams,
26
+ // that imports `createSearchParams` from `@barefootjs/client/runtime`;
27
+ // re-exporting it from the shared reactive module (same as the signal
28
+ // primitives above) means this entry and the main `@barefootjs/client` entry
29
+ // resolve to ONE signal instance — no second copy to disconnect from router
30
+ // pushes.
31
+ createSearchParams,
32
+ type SearchParamsInit,
31
33
  __bfSetServerEnvReader,
32
34
  type Reactive,
33
35
  type Signal,
@@ -49,6 +51,7 @@ export { splitProps } from '../split-props.ts'
49
51
  export { __slot, type SlotMarker } from '../slot.ts'
50
52
  export { forwardProps } from '../forward-props.ts'
51
53
  export { unwrap } from '../unwrap.ts'
54
+ export { queryHref, type QueryParams, type QueryParamValue } from '../query-href.ts'
52
55
 
53
56
  // Context API (real DOM-bound implementations; `createContext` is the
54
57
  // same pure function re-exported from `../context`).