@close-by/clay 0.2.13 → 0.2.15

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/SKILL.md CHANGED
@@ -62,6 +62,7 @@ ui.label(() => `Count: ${s.count}`);
62
62
  | API | Use when |
63
63
  |-----|----------|
64
64
  | `ui.state(initial)` | Reactive object; writes trigger tracked rebuilds |
65
+ | `ui.urlState(defaults, opts?)` | Like `state`, hydrated from / synced to the query string |
65
66
  | `ui.auto(fn)` | Rebuild a UI block when tracked state changes |
66
67
  | `ui.label(() => …)` / `bindText` | Patch text only (stable tree) |
67
68
  | `bindValue` | Two-way inputs |
@@ -94,7 +95,7 @@ ui.page('/dashboard', () => {
94
95
  ## Do not
95
96
 
96
97
  - Import React, write JSX, or add client components for screens.
97
- - Use `window`, `document`, `navigator` in page code — use `ui.clipboard`, `ui.setUrlHash`, `ui.openExternal`.
98
+ - Use `window`, `document`, `navigator` in page code — use `ui.clipboard`, `ui.setUrlHash`, `ui.getUrlSearch` / `ui.updateUrlSearch`, `ui.openExternal`.
98
99
  - Assume `let` is shared across tabs or users.
99
100
  - Use `ui.ai.*` as an LLM runtime — it is visual primitives only.
100
101
 
package/docs/README.md CHANGED
@@ -15,7 +15,8 @@ For coding agents: the repo-root [llms.txt](../llms.txt) is a short map of these
15
15
  | [Tailwind](./tailwind.md) | Zero-config utilities from page modules; optional tokens |
16
16
  | [Sample app](./tutorial.md) | Step-by-step tasks list, then two pages + shell |
17
17
  | [Concepts](./concepts.md) | Mental model: sessions, elements, patches; **canonical `state`/`auto`/`timer` recipe**; reactive `let` alternative |
18
- | [Browser APIs](./browser-apis.md) | Prefer `ui.clipboard` / hash / `openExternal` — never `window` in page code |
18
+ | [Browser APIs](./browser-apis.md) | Prefer `ui.clipboard` / hash / search / `openExternal` — never `window` in page code |
19
+ | [URL search](./url-search.md) | `ui.urlState` + query helpers for shareable filters |
19
20
  | [Production & security](./production.md) | Auth, server exposure, rate limits, boot parity |
20
21
  | [Ops patterns](./ops-patterns.md) | Dense consoles: chips, master–detail, feeds, sensitive logs, auth nav |
21
22
  | [API reference](./api.md) | `ui.*`, `Element` methods, `reactive`, helpers |
package/docs/SKILL.md CHANGED
@@ -62,6 +62,7 @@ ui.label(() => `Count: ${s.count}`);
62
62
  | API | Use when |
63
63
  |-----|----------|
64
64
  | `ui.state(initial)` | Reactive object; writes trigger tracked rebuilds |
65
+ | `ui.urlState(defaults, opts?)` | Like `state`, hydrated from / synced to the query string |
65
66
  | `ui.auto(fn)` | Rebuild a UI block when tracked state changes |
66
67
  | `ui.label(() => …)` / `bindText` | Patch text only (stable tree) |
67
68
  | `bindValue` | Two-way inputs |
@@ -94,7 +95,7 @@ ui.page('/dashboard', () => {
94
95
  ## Do not
95
96
 
96
97
  - Import React, write JSX, or add client components for screens.
97
- - Use `window`, `document`, `navigator` in page code — use `ui.clipboard`, `ui.setUrlHash`, `ui.openExternal`.
98
+ - Use `window`, `document`, `navigator` in page code — use `ui.clipboard`, `ui.setUrlHash`, `ui.getUrlSearch` / `ui.updateUrlSearch`, `ui.openExternal`.
98
99
  - Assume `let` is shared across tabs or users.
99
100
  - Use `ui.ai.*` as an LLM runtime — it is visual primitives only.
100
101
 
package/docs/api.md CHANGED
@@ -1052,7 +1052,7 @@ ui.stat([
1052
1052
 
1053
1053
  #### `ui.areaChart(props)`
1054
1054
 
1055
- Stacked area chart (Recharts). Optional card chrome via `title` / `description`. Set `interactive: true` with ISO date `xKey` values for client-side period tabs (default 7d / 30d / 90d; override with `periods`). Optional `headline: { value, trend?, trendDirection? }` for a KPI row above the chart; `loading: true` shows a skeleton. Legend is always shown. Pass `stacked: false` to overlay series instead of stacking (default `true`).
1055
+ Stacked area chart (Recharts). Optional card chrome via `title` / `description`. Set `interactive: true` with ISO date `xKey` values for client-side period tabs (default 7d / 30d / 90d; override with `periods`). Optional `headline: { value, trend?, trendDirection? }` for a KPI row above the chart; `loading: true` shows a skeleton. Legend is shown by default; pass `showLegend: false` to hide. Pass `stacked: false` to overlay series instead of stacking (default `true`).
1056
1056
 
1057
1057
  ```typescript
1058
1058
  ui.areaChart({
@@ -1071,7 +1071,7 @@ ui.areaChart({
1071
1071
 
1072
1072
  #### `ui.barChart(props)`
1073
1073
 
1074
- Bar chart with the same cartesian props as area (`data`, `xKey`, `series`, title/description/height/interactive, `headline`, `periods`, `loading`). Optional `stacked` (default `false`) and `layout: 'vertical' | 'horizontal'` (default `'vertical'` — category on X).
1074
+ Bar chart with the same cartesian props as area (`data`, `xKey`, `series`, title/description/height/interactive, `headline`, `periods`, `loading`, `showLegend?`). Optional `stacked` (default `false`) and `layout: 'vertical' | 'horizontal'` (default `'vertical'` — category on X).
1075
1075
 
1076
1076
  ```typescript
1077
1077
  ui.barChart({
@@ -1089,11 +1089,22 @@ ui.barChart({
1089
1089
 
1090
1090
  #### `ui.lineChart(props)`
1091
1091
 
1092
- Line chart with the same cartesian props as area (including interactive date ranges, headline, periods, loading).
1092
+ Line chart with the same cartesian props as area (including interactive date ranges, headline, periods, loading, `showLegend?`). Line styling:
1093
+
1094
+ | Prop | Default | Description |
1095
+ |------|---------|-------------|
1096
+ | `curve` | `'monotone'` | Recharts interpolation: `'linear'`, `'natural'`, `'step'`, `'stepBefore'`, `'stepAfter'`, `'basis'` |
1097
+ | `strokeWidth` | `2` | Line thickness in px |
1098
+ | `dots` | `false` | Show point markers |
1099
+ | `strokeDasharray` / `dashed` | — | Dashed stroke; `dashed: true` → `'4 4'` |
1100
+ | `variant` | `'default'` | `'minimal'` hides the grid for a cleaner plot |
1093
1101
 
1094
1102
  ```typescript
1095
1103
  ui.lineChart({
1096
1104
  title: 'Traffic trend',
1105
+ showLegend: false,
1106
+ curve: 'step',
1107
+ dots: true,
1097
1108
  data: monthly,
1098
1109
  xKey: 'month',
1099
1110
  series: [
@@ -1782,6 +1793,11 @@ onClick: async () => {
1782
1793
  | `ui.clipboard(content)` | `void` | Write text to clipboard (prefer over `navigator.clipboard`) |
1783
1794
  | `ui.getUrlHash()` | `string` | Current hash without `#` (from hello / last set) |
1784
1795
  | `ui.setUrlHash(hash)` | `void` | Set `location.hash` on the client |
1796
+ | `ui.getUrlSearch()` | `string` | Current search without `?` (from hello / last set) |
1797
+ | `ui.getUrlSearchParams()` | `URLSearchParams` | Mutable parse of `getUrlSearch()` |
1798
+ | `ui.setUrlSearch(search, opts?)` | `void` | Set `location.search` (`opts.mode`: `replace` \| `push`) |
1799
+ | `ui.updateUrlSearch(patch, opts?)` | `void` | Merge query keys; `null`/`''` deletes |
1800
+ | `ui.urlState(defaults, opts?)` | reactive object | `ui.state` + hydrate/sync query string (prefer this) |
1785
1801
  | `ui.openExternal(url)` | `void` | `window.open` in a new tab (`noopener`) |
1786
1802
  | `ui.theme.set(mode)` | `void` | Push `light` \| `dark` \| `system` to this client |
1787
1803
  | `ui.theme.get()` | `ThemeMode \| null` | Last value set on this session |
@@ -8,6 +8,7 @@ Page builders and event handlers run on the **server** (Bun), not in the browser
8
8
  |------|-----|
9
9
  | Copy text | `ui.clipboard(text)` |
10
10
  | Read / set URL hash | `ui.getUrlHash()` / `ui.setUrlHash(hash)` |
11
+ | Read / set URL search (query) | `ui.urlState(defaults)` (prefer) or `ui.getUrlSearch` / `ui.updateUrlSearch` |
11
12
  | Open a URL in a new tab | `ui.openExternal(url)` |
12
13
  | In-app route change | `ui.navigate(path)` |
13
14
  | Scroll | `ui.scroll.to` / `ui.scroll.intoView` |
@@ -23,17 +24,23 @@ ui.button('Copy id', {
23
24
 
24
25
  const focus = ui.getUrlHash(); // from last hello / setUrlHash
25
26
  ui.setUrlHash(traceId);
27
+
28
+ const partner = ui.getUrlSearchParams().get('partner');
29
+ ui.updateUrlSearch({ partner: 'UBER', page: null }); // omit empty defaults
26
30
  ui.openExternal('https://signoz.example/trace/' + traceId);
27
31
  ```
28
32
 
29
33
  `getUrlHash` returns the hash **without** a leading `#`. It is hydrated from the client on each `hello` (connect, reconnect, SPA navigate) and updated when you call `setUrlHash`.
30
34
 
35
+ `getUrlSearch` returns the query string **without** a leading `?` (same hydration / write path). Prefer **`ui.urlState`** for filter objects; use `ui.updateUrlSearch` for one-off merges. See [URL search](./url-search.md).
36
+
31
37
  ## Don’t
32
38
 
33
39
  ```ts
34
40
  // Wrong — server has no clipboard / location
35
41
  await navigator.clipboard.writeText(value);
36
42
  window.location.hash = traceId;
43
+ window.location.search = '?partner=UBER';
37
44
  window.open(url, '_blank');
38
45
  ```
39
46
 
@@ -61,5 +68,5 @@ With **`clay --reactive-let`**, transformed files that import known-fragile CJS
61
68
  ## Related
62
69
 
63
70
  - Helpers table: [API — dialogs and feedback](./api.md#dialogs-and-feedback)
64
- - Protocol: `clipboard`, `setUrlHash`, `openExternal` in [WebSocket protocol](./protocol.md)
71
+ - Protocol: `clipboard`, `setUrlHash`, `setUrlSearch`, `openExternal` in [WebSocket protocol](./protocol.md)
65
72
  - Phase 1 reactivity (preferred over DOM hacks): [reactive-let](./reactive-let.md)
package/docs/elements.md CHANGED
@@ -102,9 +102,9 @@ Each factory creates an `Element` with a wire `type` string. The React client ma
102
102
  | `upload` | `upload` | File picker / dropzone → `POST /upload` (progress/abort) → WS `upload` / `progress` / `error` / `abort` |
103
103
  | `stat` | `stat` | Grid of metric cards (optional per-item `sparkline`) |
104
104
  | `sparkline` | `sparkline` | KPI card with inline area/line mini chart (`headline`, `value`/`trend` shorthand) |
105
- | `areaChart` | `areachart` | Recharts stacked area (+ legend, optional interactive periods, `headline`, `loading`, `stacked?`) |
106
- | `barChart` | `barchart` | Recharts bar (`stacked?`, `layout?: 'vertical' \| 'horizontal'`, shared cartesian chrome) |
107
- | `lineChart` | `linechart` | Recharts line (+ optional interactive periods, headline, loading) |
105
+ | `areaChart` | `areachart` | Recharts stacked area (+ legend, optional interactive periods, `headline`, `loading`, `stacked?`, `showLegend?`) |
106
+ | `barChart` | `barchart` | Recharts bar (`stacked?`, `layout?: 'vertical' \| 'horizontal'`, shared cartesian chrome, `showLegend?`) |
107
+ | `lineChart` | `linechart` | Recharts line (+ interactive periods, headline, loading, `curve`/`strokeWidth`/`dots`/`dashed`/`variant`, `showLegend?`) |
108
108
  | `pieChart` | `piechart` | Recharts pie/donut (`nameKey`/`valueKey` or `series`, `innerRadius?`) |
109
109
  | `radarChart` | `radarchart` | Recharts radar (`angleKey` + `series`, optional `fillOpacity`) |
110
110
  | `radialChart` | `radialchart` | Recharts radial bar (`nameKey`/`valueKey` or stacked `series`, optional center text / angles) |
package/docs/llms.txt CHANGED
@@ -9,7 +9,7 @@ Notes for agents:
9
9
  - Public API and wire protocol are camelCase (`onClick`, `bindValue`, `setText`).
10
10
  - Page builders run once per WebSocket session (per browser tab). `let` is per-tab, not shared.
11
11
  - Update existing nodes with `setText` / `setValue` / bindings. Prefer `ui.state` + `ui.auto` (+ `ui.timer`) for async/live UIs; `ui.refreshable` for simple one-shot rebuilds. Shared or persisted state: `ui.storage` (`tab` / `user` / `app`).
12
- - Never use `window` / `navigator` in page code — use `ui.clipboard`, `ui.setUrlHash`, `ui.openExternal`.
12
+ - Never use `window` / `navigator` in page code — use `ui.clipboard`, `ui.setUrlHash`, `ui.getUrlSearch` / `ui.updateUrlSearch`, `ui.openExternal`.
13
13
  - Runtime is Bun. In this repo: `bun run build:client`, then `bun run demo` (http://localhost:4000) or `bun run clay hello.ts`.
14
14
  - `ui.ai.*` is visual primitives only — no model or streaming runtime.
15
15
  - Agent skill (Cursor / compatible tools): `@close-by/clay/SKILL.md` — copy to `.cursor/skills/clay/SKILL.md` or read from `node_modules` when installed.
package/docs/protocol.md CHANGED
@@ -19,7 +19,7 @@ On unexpected socket close, the client reconnects with exponential backoff (500m
19
19
  { "op": "hello", "path": "/examples/counter", "userId": "optional-stable-id", "hash": "optional-hash-without-hash-sign", "browserStorage": {}, "clientStorage": {}, "tabStorage": {} }
20
20
  ```
21
21
 
22
- Client connects (or sends another `hello` after navigate on the same socket) and identifies the page. Optional `userId` (from localStorage/cookie) enables `ui.storage.user` unless overridden by `resolveUserId`. Optional `hash` (no leading `#`) hydrates `ui.getUrlHash()`. Optional `browserStorage` / `clientStorage` / `tabStorage` bags hydrate `ui.storage.browser` / `ui.storage.client` / `ui.storage.tab`.
22
+ Client connects (or sends another `hello` after navigate on the same socket) and identifies the page. Optional `userId` (from localStorage/cookie) enables `ui.storage.user` unless overridden by `resolveUserId`. Optional `hash` (no leading `#`) hydrates `ui.getUrlHash()`. Optional `search` (no leading `?`) hydrates `ui.getUrlSearch()`. Optional `browserStorage` / `clientStorage` / `tabStorage` bags hydrate `ui.storage.browser` / `ui.storage.client` / `ui.storage.tab`.
23
23
 
24
24
  Sent on connect and after client-side navigation. The React client keeps matching `app` shell chrome mounted across remounts; only inset content is replaced.
25
25
 
@@ -163,6 +163,14 @@ Client writes `content` with `navigator.clipboard.writeText` (shows an error toa
163
163
 
164
164
  Client sets `location.hash` via `history.replaceState` (`hash` has no leading `#`; empty string clears). Hydrated the other way on `hello.hash`. Prefer `ui.setUrlHash` / `ui.getUrlHash`.
165
165
 
166
+ ### `setUrlSearch`
167
+
168
+ ```json
169
+ { "op": "setUrlSearch", "search": "tab=orders&partner=UBER", "mode": "replace" }
170
+ ```
171
+
172
+ Client sets `location.search` via `history.replaceState` or `pushState` (`mode`, default `replace`). `search` has no leading `?`; empty string clears. Pathname and hash are preserved. Hydrated the other way on `hello.search`. Prefer `ui.setUrlSearch` / `ui.updateUrlSearch` / `ui.getUrlSearch`. See [URL search](./url-search.md).
173
+
166
174
  ### `openExternal`
167
175
 
168
176
  ```json
@@ -0,0 +1,97 @@
1
+ # URL search (query string) helpers
2
+
3
+ Ops consoles often need **shareable filter state** in the address bar
4
+ (`?tab=orders&partner=UBER&from=2026-01-01`). Page builders run on the server, so
5
+ they must not read `window.location`. Clay mirrors the existing hash helpers.
6
+
7
+ ## Prefer `ui.urlState`
8
+
9
+ Like `ui.state`, but hydrated from the query string and write-through synced:
10
+
11
+ ```ts
12
+ import { ui } from '@close-by/clay';
13
+
14
+ ui.page('/lastmile', () => {
15
+ const filters = ui.urlState({
16
+ tab: 'dashboard',
17
+ partner: '',
18
+ from: '',
19
+ page: 1,
20
+ });
21
+
22
+ // ?partner=UBER&page=2 — defaults (dashboard / '' / 1) are omitted
23
+ filters.partner = 'UBER';
24
+ filters.page = 2;
25
+
26
+ ui.select({
27
+ label: 'Partner',
28
+ value: filters.partner || '__all__',
29
+ options: [
30
+ { value: '__all__', label: 'All' },
31
+ { value: 'UBER', label: 'Uber Eats' },
32
+ ],
33
+ onChange: (v) => {
34
+ filters.partner = v === '__all__' ? '' : v;
35
+ },
36
+ });
37
+ });
38
+ ```
39
+
40
+ | Option | Default | Role |
41
+ |--------|---------|------|
42
+ | `omitDefaults` | `true` | Drop keys whose value equals the default |
43
+ | `mode` | `'replace'` | History write (`'push'` when Back should undo) |
44
+ | `keys` | identity | Remap state keys → query names (`{ page: 'p' }`) |
45
+
46
+ Works with `ui.auto` / `bindValue` the same way as `ui.state`.
47
+
48
+ ## Low-level helpers
49
+
50
+ Escape hatches when you need a one-off read/write without a reactive object:
51
+
52
+ | Helper | Role |
53
+ |--------|------|
54
+ | `ui.getUrlSearch()` | Current search **without** leading `?` (from `hello` / last set) |
55
+ | `ui.getUrlSearchParams()` | Mutable `URLSearchParams` copy of that string |
56
+ | `ui.setUrlSearch(search, opts?)` | Replace the whole query string (empty clears) |
57
+ | `ui.updateUrlSearch(patch, opts?)` | Merge keys; `null` / `undefined` / `''` deletes |
58
+
59
+ `opts.mode` is `'replace'` (default) or `'push'`. Prefer **replace** for filter
60
+ tweaks so Back is not flooded.
61
+
62
+ ## Protocol
63
+
64
+ **Client → server** (`hello`):
65
+
66
+ ```json
67
+ { "op": "hello", "path": "/lastmile", "hash": "", "search": "tab=orders&partner=UBER" }
68
+ ```
69
+
70
+ **Server → client**:
71
+
72
+ ```json
73
+ { "op": "setUrlSearch", "search": "tab=orders&partner=UBER", "mode": "replace" }
74
+ ```
75
+
76
+ Client writes `pathname + ?search + hash` via `history.replaceState` / `pushState`.
77
+ Hash is preserved (same as `setUrlHash` preserves search).
78
+
79
+ ## Path vs query
80
+
81
+ | Concern | Prefer |
82
+ |---------|--------|
83
+ | Resource identity | Path (`/orders/:id`) via `ui.page` / `ui.navigate` |
84
+ | Active tab, filters, sort, page | **Query string** (`ui.urlState`) |
85
+ | In-page focus / trace id | Hash (`ui.setUrlHash`) when you already use it |
86
+
87
+ ## Limits / follow-ups
88
+
89
+ - Hydration runs on each `hello` (connect, reconnect, SPA path change). Pure
90
+ Back/Forward that only changes the query does **not** remount yet — same
91
+ limitation as hash today. A future `urlchange` client event (or popstate →
92
+ hello) would close that gap and re-hydrate `urlState` in place.
93
+
94
+ ## Related
95
+
96
+ - [Browser APIs](./browser-apis.md)
97
+ - [WebSocket protocol](./protocol.md) — `hello.search`, `setUrlSearch`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@close-by/clay",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
4
4
  "description": "NiceGUI-like ui.* facade for Clay apps",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,10 +37,10 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@close-by/clay-compiler": "0.2.13",
41
- "@close-by/clay-core": "0.2.13",
42
- "@close-by/clay-components": "0.2.13",
43
- "@close-by/clay-server": "0.2.13",
40
+ "@close-by/clay-compiler": "0.2.15",
41
+ "@close-by/clay-core": "0.2.15",
42
+ "@close-by/clay-components": "0.2.15",
43
+ "@close-by/clay-server": "0.2.15",
44
44
  "@tailwindcss/cli": "^4.3.3",
45
45
  "tailwindcss": "^4.3.3"
46
46
  }
package/src/index.ts CHANGED
@@ -237,6 +237,9 @@ import {
237
237
  type ChartPeriod,
238
238
  type ChartReferenceLine,
239
239
  type ChartReferenceArea,
240
+ type LineCurve,
241
+ type LineChartVariant,
242
+ type LineChartStyleProps,
240
243
  type SparklineProps,
241
244
  type CardProps,
242
245
  type DialogProps,
@@ -440,6 +443,10 @@ import {
440
443
  runJavaScript as runJavaScriptCore,
441
444
  getUrlHash as getUrlHashCore,
442
445
  setUrlHash as setUrlHashCore,
446
+ getUrlSearch as getUrlSearchCore,
447
+ getUrlSearchParams as getUrlSearchParamsCore,
448
+ setUrlSearch as setUrlSearchCore,
449
+ updateUrlSearch as updateUrlSearchCore,
443
450
  openExternal as openExternalCore,
444
451
  scroll as scrollCore,
445
452
  timer as timerCore,
@@ -448,6 +455,7 @@ import {
448
455
  reactive as reactiveCore,
449
456
  state as stateCore,
450
457
  subscribe as subscribeCore,
458
+ urlState as urlStateCore,
451
459
  auto as autoCore,
452
460
  draft as draftCore,
453
461
  validate as validateCore,
@@ -465,6 +473,8 @@ import {
465
473
  type ThemeMode,
466
474
  type DraftStorage,
467
475
  type DraftOptions,
476
+ type UrlStateOptions,
477
+ type UrlStateValue,
468
478
  } from '@close-by/clay-core';
469
479
  import {
470
480
  ClayServer,
@@ -1590,6 +1600,35 @@ export function setUrlHash(hash: string): void {
1590
1600
  setUrlHashCore(hash);
1591
1601
  }
1592
1602
 
1603
+ /** URL search without `?` (from hello / last `setUrlSearch`). Prefer over `window.location`. */
1604
+ export function getUrlSearch(): string {
1605
+ return getUrlSearchCore();
1606
+ }
1607
+
1608
+ /** Parse {@link getUrlSearch} as `URLSearchParams` (mutable copy). */
1609
+ export function getUrlSearchParams(): URLSearchParams {
1610
+ return getUrlSearchParamsCore();
1611
+ }
1612
+
1613
+ /** Set the browser query string. Prefer over `window.history` / `location.search`. */
1614
+ export function setUrlSearch(
1615
+ search: string,
1616
+ opts?: { mode?: 'replace' | 'push' },
1617
+ ): void {
1618
+ setUrlSearchCore(search, opts);
1619
+ }
1620
+
1621
+ /**
1622
+ * Merge keys into the current query string.
1623
+ * `null` / `undefined` / `''` deletes the key.
1624
+ */
1625
+ export function updateUrlSearch(
1626
+ patch: Record<string, string | number | boolean | null | undefined>,
1627
+ opts?: { mode?: 'replace' | 'push' },
1628
+ ): void {
1629
+ updateUrlSearchCore(patch, opts);
1630
+ }
1631
+
1593
1632
  /** Open a URL in a new tab. Prefer over `runJavaScript('window.open…')`. */
1594
1633
  export function openExternal(url: string): void {
1595
1634
  openExternalCore(url);
@@ -1627,6 +1666,18 @@ export const reactive = reactiveCore;
1627
1666
  /** Alias for `reactive` — NiceGUI-ish mutable page state. Prefer `ui.state`. */
1628
1667
  export const state = stateCore;
1629
1668
 
1669
+ /**
1670
+ * Like `ui.state`, but hydrated from the query string and write-through synced
1671
+ * via `ui.updateUrlSearch`. Prefer for shareable filters / tabs.
1672
+ * See {@link UrlStateOptions}.
1673
+ */
1674
+ export function urlState<T extends Record<string, UrlStateValue>>(
1675
+ defaults: T,
1676
+ opts?: UrlStateOptions<T>,
1677
+ ): T {
1678
+ return urlStateCore(defaults, opts);
1679
+ }
1680
+
1630
1681
  /** Listen for a reactive property change. Prefer this over importing from `@close-by/clay-core`. */
1631
1682
  export const subscribe = subscribeCore;
1632
1683
 
@@ -1912,6 +1963,10 @@ export const ui = {
1912
1963
  clipboard,
1913
1964
  getUrlHash,
1914
1965
  setUrlHash,
1966
+ getUrlSearch,
1967
+ getUrlSearchParams,
1968
+ setUrlSearch,
1969
+ updateUrlSearch,
1915
1970
  openExternal,
1916
1971
  runJavaScript,
1917
1972
  scroll,
@@ -1920,6 +1975,7 @@ export const ui = {
1920
1975
  theme,
1921
1976
  reactive,
1922
1977
  state,
1978
+ urlState,
1923
1979
  subscribe,
1924
1980
  draft,
1925
1981
  validate,