@close-by/clay 0.2.18 → 0.2.20
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/docs/README.md +1 -0
- package/docs/api.md +57 -0
- package/docs/demo-deployment.md +64 -0
- package/docs/examples.md +7 -1
- package/docs/maps.md +214 -0
- package/docs/protocol.md +1 -0
- package/docs/updating-in-place.md +27 -2
- package/package.json +5 -5
- package/src/index.ts +51 -11
package/docs/README.md
CHANGED
|
@@ -22,6 +22,7 @@ For coding agents: the repo-root [llms.txt](../llms.txt) is a short map of these
|
|
|
22
22
|
| [Ops patterns](./ops-patterns.md) | Dense consoles: chips, master–detail, feeds, sensitive logs, auth nav |
|
|
23
23
|
| [API reference](./api.md) | `ui.*`, `Element` methods, `reactive`, helpers |
|
|
24
24
|
| [Elements](./elements.md) | All element types and props |
|
|
25
|
+
| [Maps](./maps.md) | `ui.map`: MapLibre markers, routes, arcs, GeoJSON, clusters; palette theming and in-place updates |
|
|
25
26
|
| [WebSocket protocol](./protocol.md) | Client ↔ server message formats |
|
|
26
27
|
| [Architecture](./architecture.md) | Packages, data flow, and ownership |
|
|
27
28
|
| [Examples](./examples.md) | Demo apps and patterns |
|
package/docs/api.md
CHANGED
|
@@ -91,6 +91,7 @@ ui.run({
|
|
|
91
91
|
| `title` | `string` | `'Clay'` | HTML `<title>` |
|
|
92
92
|
| `clientDir` | `string` | `resolveClayClientDir()` | Built Vite assets (`@close-by/clay-cli/client-dist` when installed). Prefer `resolveClayClientDir()` over hardcoding `node_modules` paths — see [Boot](./boot.md). |
|
|
93
93
|
| `css` | `string \| string[]` | | Extra CSS file path(s) served after the client bundle (absolute or cwd-relative). Override theme tokens without rebuilding the client. |
|
|
94
|
+
| `sound` | `boolean` | | Play short synthesized interface sounds by default (`false`). See [Sound](#sound). |
|
|
94
95
|
| `app` | `AppProps` | | Global dashboard shell; client keeps chrome sticky across navigate |
|
|
95
96
|
| `authSecret` | `string` | | Enables signed auth cookies (`POST`/`DELETE /auth/session`), `ui.establishAuthSession` / `ui.clearAuthSession`, and default `resolveUserId` from the cookie |
|
|
96
97
|
| `authCookieMaxAgeSec` | `number` | `43200` (12h) | Auth cookie Max-Age |
|
|
@@ -1219,6 +1220,21 @@ ui.radialChart({
|
|
|
1219
1220
|
});
|
|
1220
1221
|
```
|
|
1221
1222
|
|
|
1223
|
+
#### Live chart updates
|
|
1224
|
+
|
|
1225
|
+
Every chart factory and `ui.chart.*` builder returns a `ChartElement`:
|
|
1226
|
+
|
|
1227
|
+
| Method | What it sends |
|
|
1228
|
+
|--------|---------------|
|
|
1229
|
+
| `appendData(rows, { max? })` | `appendRows` with only the new rows; client trims to the last `max` |
|
|
1230
|
+
| `setData(rows)` | `updateProps` (`data`) |
|
|
1231
|
+
| `upsertData(row, { key?, max? })` | Replaces the row with the same `key` (default `xKey`, or `nameKey` for pies) or appends it |
|
|
1232
|
+
| `setSeries(series)` / `setHeadline(h)` / `setTitle(t)` / `setDescription(d)` / `setLoading(b)` | `updateProps` for that prop |
|
|
1233
|
+
|
|
1234
|
+
`ui.stat(items)` returns a `StatElement` with `setItems(items)`, `updateItem(titleOrIndex, patch)` and `appendSparkline(titleOrIndex, points, { max? })`. Each sends one `updateProps` (`items`).
|
|
1235
|
+
|
|
1236
|
+
All charts accept `animate?: boolean` (default `true`). See [Updating in place](./updating-in-place.md#live-charts-appenddata--setdata--upsertdata).
|
|
1237
|
+
|
|
1222
1238
|
#### Structured charts (`ui.chart`)
|
|
1223
1239
|
|
|
1224
1240
|
Optional sugar over the props APIs above. Mode-first builders compile to the same `Element` types — legacy `ui.areaChart` / `ui.pieChart` / etc. remain fully supported.
|
|
@@ -1804,6 +1820,10 @@ onClick: async () => {
|
|
|
1804
1820
|
| `ui.theme.palette(name)` | `void` | Switch colour palette for this client (`kiln`, `cobalt`, `ocean`, `forest`, `orchid`, `citrus`, `graphite`, `neon`, `sky`, `navy`, `indigo`, `denim`, `midnight`, or a custom one); remembered across reloads |
|
|
1805
1821
|
| `ui.theme.getPalette()` | `ThemePalette \| null` | Last palette set, else the one the client reported on connect |
|
|
1806
1822
|
| `ui.theme.palettes` | `readonly string[]` | Built-in palette names |
|
|
1823
|
+
| `ui.sound.set(on)` | `void` | Turn automatic interface sounds on/off for this client; remembered across reloads. See [Sound](#sound) |
|
|
1824
|
+
| `ui.sound.volume(v)` | `void` | Master volume `0`–`1` (default `0.5`); remembered across reloads |
|
|
1825
|
+
| `ui.sound.play(name)` | `void` | Play one cue now, even when automatic sounds are off |
|
|
1826
|
+
| `ui.sound.names` | `readonly string[]` | Built-in cue names |
|
|
1807
1827
|
| `ui.runJavaScript(code)` | `void` | Eval trusted snippet in the browser |
|
|
1808
1828
|
| `ui.scroll.to(opts)` | `void` | Scroll window (`top` / `bottom` / px) |
|
|
1809
1829
|
| `ui.scroll.intoView(selector, opts?)` | `void` | `Element.scrollIntoView` via selector |
|
|
@@ -1918,6 +1938,7 @@ All layout helpers accept either `(fn, props?)` or `(props, fn)`.
|
|
|
1918
1938
|
| `ui.hero` | Centered hero region |
|
|
1919
1939
|
| `ui.card` | Card; callback receives the card element |
|
|
1920
1940
|
| `ui.app` | SPA shell: sidebar nav + centered main content |
|
|
1941
|
+
| `ui.map` | Live map (MapLibre): markers, routes, arcs, GeoJSON, clusters, popup. See [Maps](./maps.md) |
|
|
1921
1942
|
|
|
1922
1943
|
#### `ui.app(props, fn)`
|
|
1923
1944
|
|
|
@@ -2016,6 +2037,15 @@ ui.container(() => {
|
|
|
2016
2037
|
}, { centered: true, width: 'md' });
|
|
2017
2038
|
```
|
|
2018
2039
|
|
|
2040
|
+
#### `ui.map(props?)`
|
|
2041
|
+
|
|
2042
|
+
A live map: MapLibre with markers, routes, arcs, GeoJSON layers, clusters and a popup. Setters (`setMarkers`, `upsertMarker`, `updateRoute`, `flyTo`, `fitBounds`, ...) update it in place. Props, tones, events and layout are in the [Maps guide](./maps.md).
|
|
2043
|
+
|
|
2044
|
+
```typescript
|
|
2045
|
+
const fleet = ui.map({ fill: true, markers: vehicles(), onMarkerClick: (m) => select(m.id) });
|
|
2046
|
+
fleet.fitBounds('markers', { padding: 48 });
|
|
2047
|
+
```
|
|
2048
|
+
|
|
2019
2049
|
---
|
|
2020
2050
|
|
|
2021
2051
|
## `Element` methods (`@close-by/clay-core`)
|
|
@@ -2183,6 +2213,33 @@ import { button, input /* … */ } from '@close-by/clay-components';
|
|
|
2183
2213
|
|
|
2184
2214
|
---
|
|
2185
2215
|
|
|
2216
|
+
## Sound
|
|
2217
|
+
|
|
2218
|
+
Clay can answer interactions with short interface sounds, synthesized in the browser with Web Audio (no audio files). They're **off by default**. Turn them on for every visitor with `createServer({ sound: true })`, or per session with `ui.sound.set(true)`. The client remembers the last choice.
|
|
2219
|
+
|
|
2220
|
+
Once sound is on, a mute button appears next to the Clay connection chip in the bottom-left corner, so each user can silence it. A user's mute choice is remembered for their browser. Apps that never turn sound on don't show the button.
|
|
2221
|
+
|
|
2222
|
+
When enabled, a page-wide listener picks the cue from the element, so components need no wiring:
|
|
2223
|
+
|
|
2224
|
+
| Interaction | Cue |
|
|
2225
|
+
|---|---|
|
|
2226
|
+
| Button, link, `role="button"` | `tap` (`soft` for `ghost` / `link` variants, `destructive` for the destructive variant) |
|
|
2227
|
+
| Tab, menu item, option, radio | `select` (pitch rises with position) |
|
|
2228
|
+
| Switch, checkbox | `toggleOn` / `toggleOff` |
|
|
2229
|
+
| Disabled control (when it still receives the press) | `blocked` |
|
|
2230
|
+
| Dialog / sheet / alert dialog opens or closes | `open` / `close` |
|
|
2231
|
+
| Slider value changes | `tick` |
|
|
2232
|
+
| OTP digit typed | `key` |
|
|
2233
|
+
| `ui.notify` toast | `success` / `error` / `warning` / `notification` by type |
|
|
2234
|
+
| `ui.clipboard` / code block copy | `copy` |
|
|
2235
|
+
|
|
2236
|
+
Set `data-sound="<cue>"` on an element to override its cue, or `data-sound="none"` to silence it. Use `ui.sound.play(name)` to play a cue from server code, for example when a long job finishes. Cues: `tap`, `soft`, `select`, `destructive`, `blocked`, `toggleOn`, `toggleOff`, `open`, `close`, `tick`, `key`, `copy`, `success`, `error`, `warning`, `notification`.
|
|
2237
|
+
|
|
2238
|
+
```ts
|
|
2239
|
+
// A settings switch that lets each user turn sounds on or off
|
|
2240
|
+
ui.switch({ label: 'Interface sounds', onChange: (on) => ui.sound.set(on) });
|
|
2241
|
+
```
|
|
2242
|
+
|
|
2186
2243
|
## DuckDB (`@close-by/clay-duckdb`)
|
|
2187
2244
|
|
|
2188
2245
|
See [DuckDB](./duckdb.md) for the multi-database wrapper (`connect`, `attach`, CRUD).
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Demo deployment on Pi
|
|
2
|
+
|
|
3
|
+
The demo runs at https://clay.close-by.org/ on Pi as the `bad-ui-demo` systemd service. It runs Bun as the `pi` user from `/home/pi/bad-ui-demo` on port 4000. The existing Flock Cloudflare tunnel forwards `clay.close-by.org` to `http://localhost:4000`; the Cloudflare DNS record is a proxied CNAME to that tunnel.
|
|
4
|
+
|
|
5
|
+
## Update the demo
|
|
6
|
+
|
|
7
|
+
From the repository root on a machine with SSH access to the `pi` host, copy the working tree and rebuild on Pi:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
tar --exclude-vcs --exclude=node_modules --exclude=dist-pack \
|
|
11
|
+
--exclude=.env --exclude='.env.*' --exclude=.badui-user-data \
|
|
12
|
+
--exclude=.clay-user-data --exclude=.clay --exclude=.tmp \
|
|
13
|
+
-czf /tmp/clay-demo-deploy.tgz .
|
|
14
|
+
scp /tmp/clay-demo-deploy.tgz pi:/tmp/clay-demo-deploy.tgz
|
|
15
|
+
ssh pi 'tar -xzf /tmp/clay-demo-deploy.tgz -C ~/bad-ui-demo && \
|
|
16
|
+
cd ~/bad-ui-demo && export PATH="$HOME/.local/bin:$PATH" && \
|
|
17
|
+
bun install --frozen-lockfile && bun run build:client && \
|
|
18
|
+
sudo systemctl restart bad-ui-demo'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This copies the current working tree, including uncommitted changes. Extraction does not delete files removed from the source tree; remove obsolete deployed files deliberately when needed. The service keeps running the previous process until the restart, but the copy and build happen in its working directory.
|
|
22
|
+
|
|
23
|
+
Check the service and public endpoint:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
ssh pi 'systemctl status bad-ui-demo --no-pager; journalctl -u bad-ui-demo -n 50 --no-pager'
|
|
27
|
+
curl -I https://clay.close-by.org/
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Service and tunnel setup
|
|
31
|
+
|
|
32
|
+
The service is enabled with `sudo systemctl enable --now bad-ui-demo`. Its unit is `/etc/systemd/system/bad-ui-demo.service`:
|
|
33
|
+
|
|
34
|
+
```ini
|
|
35
|
+
[Unit]
|
|
36
|
+
Description=Clay demo
|
|
37
|
+
After=network.target
|
|
38
|
+
|
|
39
|
+
[Service]
|
|
40
|
+
User=pi
|
|
41
|
+
WorkingDirectory=/home/pi/bad-ui-demo
|
|
42
|
+
Environment=PORT=4000
|
|
43
|
+
Environment=PATH=/home/pi/.local/bin:/usr/local/bin:/usr/bin:/bin
|
|
44
|
+
EnvironmentFile=/home/pi/.config/bad-ui-demo/service.env
|
|
45
|
+
ExecStart=/home/pi/.local/bin/bun apps/demo/src/main.ts
|
|
46
|
+
Restart=on-failure
|
|
47
|
+
RestartSec=2
|
|
48
|
+
|
|
49
|
+
[Install]
|
|
50
|
+
WantedBy=multi-user.target
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The environment file contains `CLAY_AUTH_SECRET`; keep it private and stable across restarts. To create it on a fresh deployment, use `umask 077`, create `/home/pi/.config/bad-ui-demo`, and write `CLAY_AUTH_SECRET=` followed by a random secret (for example, from `openssl rand -hex 32`). Changing it invalidates existing signed sessions. After changing a unit, run `sudo systemctl daemon-reload` and restart the service.
|
|
54
|
+
|
|
55
|
+
The tunnel configuration is `/etc/cloudflared/flock.yml`, managed by `flock-tunnel.service`. Its Clay ingress entry must precede the final catch-all:
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
- hostname: clay.close-by.org
|
|
59
|
+
service: http://localhost:4000
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Validate changes with `sudo cloudflared tunnel --config /etc/cloudflared/flock.yml ingress validate`, then restart `flock-tunnel`. This is shared with Flock, so preserve its existing ingress entries. The DNS CNAME must point to the existing tunnel's `<tunnel-id>.cfargotunnel.com` and be proxied; the tunnel credentials on Pi alone do not grant permission to edit Cloudflare DNS.
|
|
63
|
+
|
|
64
|
+
This is a public demo with example authentication and data, not a hardened production application. See [Production & security](./production.md) before deploying real data or applications.
|
package/docs/examples.md
CHANGED
|
@@ -7,7 +7,7 @@ After `bun run build:client && bun run demo` (or `bun run demo:cli`), open http:
|
|
|
7
7
|
|
|
8
8
|
## Sidebar groups
|
|
9
9
|
|
|
10
|
-
Each page sets `group` / `groupIcon` in its `pageMeta`, so the sidebar and the home page's catalog share one structure: **Start here** (themes, fill layout, counter, todo, kitchen sink) → **Reactivity** → **Data** → **Forms & controls** → **Overlays & layout** → **Apps** → **Account** → **ReUI batches**. Order within a group is `pageMeta.order`.
|
|
10
|
+
Each page sets `group` / `groupIcon` in its `pageMeta`, so the sidebar and the home page's catalog share one structure: **Start here** (themes, fill layout, counter, todo, kitchen sink) → **Reactivity** → **Data** → **Maps** (gallery, delivery tracker, store locator, globe, uptime monitor) → **Forms & controls** → **Overlays & layout** → **Apps** → **Account** → **ReUI batches**. Order within a group is `pageMeta.order`.
|
|
11
11
|
|
|
12
12
|
## Catalog
|
|
13
13
|
|
|
@@ -26,6 +26,7 @@ Each page sets `group` / `groupIcon` in its `pageMeta`, so the sidebar and the h
|
|
|
26
26
|
| `/examples/reui-batches` | `ReuiBatchesDemo.ts` | ReUI batches 3–6: `buttonGroup`, `empty`, `pagination`, `filterBar`, form inputs, `navigationMenu`, `notice`, `stat`+sparkline, `eventCalendar`, datatable `showFilterChips` |
|
|
27
27
|
| `/examples/reui-batch-7` | `ReuiBatch7Demo.ts` | ReUI batch 7: `inputGroup`, `toggle`, `descriptionList`, `staticTable`, `aspectRatio`, `itemList`, `checkboxGroup` |
|
|
28
28
|
| `/examples/charts` | `ChartDemo.ts` | `ui.chart.*`, `ui.sparkline`, composed/scatter charts |
|
|
29
|
+
| `/examples/live-charts` | `LiveCharts.ts` | Live charts: `appendData` sliding windows, `setData` tweens, `upsertData` buckets, live gauge and sparklines |
|
|
29
30
|
| `/examples/slider-demo` | `SliderDemo.ts` | Slider, checkbox, select + bindings |
|
|
30
31
|
| `/examples/themes` | `ThemesDemo.ts` | Palette gallery: `ui.theme.palette`, light/dark, component preview |
|
|
31
32
|
| `/examples/layout` | `LayoutDemo.ts` | Fill layout: `fillViewport`, `grow`, `scroll`, `ui.grid` |
|
|
@@ -44,6 +45,11 @@ Each page sets `group` / `groupIcon` in its `pageMeta`, so the sidebar and the h
|
|
|
44
45
|
| `/examples/gantt` | `GanttDemo.ts` | `ui.gantt` owned rows/dates/markers/deps, cross-row drag, header double-click markers, `itemMove` / `markerAdd` settle |
|
|
45
46
|
| `/examples/flow` | `FlowDemo.ts` | Four `ui.flow` demos: ETL with in-node controls + labeled edges + dagre auto-layout, multi-handle approval branches, fan-in/out + `addNode` dynamic stages, group/`parentId` nesting (flow-owned edges/positions; no outer diagram `ui.auto`) |
|
|
46
47
|
| `/examples/data-clients` | `DataClientsDemo.ts` | DuckDB / Kibana / ClickHouse integration story (mock-friendly) |
|
|
48
|
+
| `/examples/maps` | `MapsGallery.ts` | `ui.map` gallery: markers and popups, routes, arcs, choropleth, clusters, compact analytics map |
|
|
49
|
+
| `/examples/delivery-tracker` | `DeliveryTracker.ts` | `ui.map` route progress and courier marker: `updateRoute` / `upsertMarker` on a timer, `fill` layout |
|
|
50
|
+
| `/examples/store-locator` | `StoreLocator.ts` | `ui.map` filtered markers, `flyTo` / `fitBounds('markers')`, popups; list and map select each other by key |
|
|
51
|
+
| `/examples/globe` | `CommunityGlobe.ts` | `ui.map` globe projection, arcs, `setMarkers` / `setArcs` on a 30 s timer |
|
|
52
|
+
| `/examples/uptime` | `UptimeMonitor.ts` | `ui.map` status markers on a globe, refreshed every 2 s with `setMarkers` |
|
|
47
53
|
| `/examples/kitchen-sink` | `KitchenSink.ts` | ShadCN catalog preview (client `KitchenSink`) |
|
|
48
54
|
|
|
49
55
|
Each page file exports optional `pageMeta` (`label`, `icon`, `order`, optional `nav: false`, optional `roles`) for `ui.navFromPages()`.
|
package/docs/maps.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Maps
|
|
2
|
+
|
|
3
|
+
`ui.map(props)` is a live map with markers, routes, arcs, GeoJSON layers, clusters, heatmaps and a popup. It renders with [MapLibre GL](https://maplibre.org/) through a vendored copy of [mapcn](https://mapcn.vercel.app) (MIT). You create the map on the server, change it with setters, and handle clicks on the server.
|
|
4
|
+
|
|
5
|
+
- **Lazy.** The client loads the map code only when a page renders a map (`React.lazy` in the element renderer). Other pages do not download MapLibre.
|
|
6
|
+
- **Worker bundled.** The MapLibre worker ships with the client build. No CDN script is loaded.
|
|
7
|
+
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
import { ui } from '@close-by/clay';
|
|
12
|
+
|
|
13
|
+
ui.page('/fleet', () => {
|
|
14
|
+
const fleet = ui.map({
|
|
15
|
+
center: [-9.14, 38.72],
|
|
16
|
+
zoom: 11,
|
|
17
|
+
height: '28rem',
|
|
18
|
+
markers: [
|
|
19
|
+
{ id: 'hq', lngLat: [-9.142, 38.7108], label: 'HQ', popup: { title: 'Head office' } },
|
|
20
|
+
{ id: 'truck-1', lngLat: [-9.1393, 38.7097], tone: 'chart-2', icon: 'truck', pulse: true },
|
|
21
|
+
],
|
|
22
|
+
onMarkerClick: (marker) => ui.notify(`Selected ${marker.id}`),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
fleet.fitBounds('markers', { padding: 48 });
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Coordinates are always `[longitude, latitude]`.
|
|
30
|
+
|
|
31
|
+
## Theming
|
|
32
|
+
|
|
33
|
+
- **Light and dark.** The basemap follows the page theme. Markers, routes, arcs and fills take their colours from the active palette.
|
|
34
|
+
- **Tones.** Colour props accept a tone name or a CSS colour such as `#2563eb`:
|
|
35
|
+
|
|
36
|
+
| Tone | Palette token |
|
|
37
|
+
|------|---------------|
|
|
38
|
+
| `primary` | `--primary` (default for markers, routes and fills) |
|
|
39
|
+
| `chart-1` … `chart-5` | `--chart-1` … `--chart-5` |
|
|
40
|
+
| `destructive` | `--destructive` |
|
|
41
|
+
| `muted` | `--muted-foreground` |
|
|
42
|
+
| `foreground` | `--foreground` |
|
|
43
|
+
|
|
44
|
+
- **Live palette changes.** The map watches the root element's `class`, `data-palette` and `style`, and the system colour scheme. A palette change re-resolves the colours in place. The map is not remounted, so the camera and open popups stay put.
|
|
45
|
+
- **`tint`** (default `true`) lightly tints the basemap toward the palette: background, water, and parks or green areas.
|
|
46
|
+
- **`basemap`** (default `'auto'`) pins the basemap to `'light'` or `'dark'`, whatever the page theme.
|
|
47
|
+
- **`styles`** replaces the basemap style URLs: `{ light?: string; dark?: string }`. See [Tiles](#tiles).
|
|
48
|
+
|
|
49
|
+
## Props
|
|
50
|
+
|
|
51
|
+
| Prop | Type | Default | Notes |
|
|
52
|
+
|------|------|---------|-------|
|
|
53
|
+
| `center` | `LngLat` | `[0, 20]` | Initial centre, read at mount. See [Camera](#camera) |
|
|
54
|
+
| `zoom` | `number` | `1.5` | Initial zoom |
|
|
55
|
+
| `pitch` / `bearing` | `number` | `0` | Initial pitch and bearing |
|
|
56
|
+
| `projection` | `'mercator' \| 'globe'` | `'mercator'` | |
|
|
57
|
+
| `height` | `string` | `'24rem'` | CSS length. Ignored with `fill` |
|
|
58
|
+
| `fill` | `boolean` | `false` | Fill the parent. See [Layout](#layout) |
|
|
59
|
+
| `interactive` | `boolean` | `true` | `false` disables pan, zoom and click |
|
|
60
|
+
| `basemap` | `'auto' \| 'light' \| 'dark'` | `'auto'` | |
|
|
61
|
+
| `styles` | `{ light?, dark? }` | CARTO styles | Custom MapLibre style URLs |
|
|
62
|
+
| `tint` | `boolean` | `true` | Palette tint on the basemap |
|
|
63
|
+
| `controls` | `MapControls \| false` | `{ zoom: true, position: 'bottom-right' }` | Object values merge over the default. `false` hides all controls |
|
|
64
|
+
| `markers` | `MapMarkerDatum[]` | `[]` | |
|
|
65
|
+
| `routes` | `MapRouteDatum[]` | `[]` | |
|
|
66
|
+
| `arcs` | `MapArcGroup[]` | `[]` | |
|
|
67
|
+
| `geojson` | `MapGeoJSONLayer[]` | `[]` | |
|
|
68
|
+
| `clusters` | `MapClusterGroup[]` | `[]` | |
|
|
69
|
+
| `heatmaps` | `MapHeatmapLayer[]` | `[]` | |
|
|
70
|
+
| `popup` | `{ lngLat, title?, description? } \| null` | closed | Standalone popup |
|
|
71
|
+
|
|
72
|
+
`MapControls` is `{ zoom?, compass?, locate?, fullscreen?, position? }`. `compass`, `locate` and `fullscreen` are off by default. `position` is `top-left`, `top-right`, `bottom-left` or `bottom-right`.
|
|
73
|
+
|
|
74
|
+
## Data layers
|
|
75
|
+
|
|
76
|
+
**Markers.** `id` (required, unique) and `lngLat` are required. Optional: `tone`, `label` (always visible), `tooltip` (on hover), `popup` (on click: `title`, `description`, `meta: [{ label, value }]`), `icon` (Lucide kebab-case name, drawn in a 28 px pin), `size` (`sm` / `md` / `lg` dots of 10 / 14 / 20 px, used when there is no icon), `pulse` (animated ring), `draggable`.
|
|
77
|
+
|
|
78
|
+
**Routes.** `id` and `coordinates: [lng, lat][]` are required. Optional: `tone`, `width` (px), `opacity`, `dashed`, `progress` (0..1: draws a progress overlay and a marker there), `progressIcon` (default `navigation`), `progressMarker` (default `true`; set `false` when you track the vehicle with your own marker), `progressTone` (colour of the travelled part, default `primary`), `active` (emphasise the selected route), `showEndpoints` (start ring and end dot). Change one route with `updateRoute` (see [Live data](#live-data--in-place-updates)).
|
|
79
|
+
|
|
80
|
+
**Arcs.** Grouped. A group has `id`, `tone`, `width`, `curvature` and `data: { id, from, to, label? }[]`. Arc ids are unique within their group.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
ui.map({
|
|
84
|
+
projection: 'globe',
|
|
85
|
+
arcs: [{ id: 'lanes', tone: 'chart-1', data: [{ id: 'lis-mad', from: [-9.14, 38.72], to: [-3.7, 40.42], label: 'Lisbon to Madrid' }] }],
|
|
86
|
+
onArcClick: (arc, group) => ui.notify(`${arc.label ?? arc.id} (${group.id})`),
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**GeoJSON.** `{ id, data, fillTone?, lineTone?, fillOpacity?, choropleth?, interactive?, promoteId? }`. `data` is a FeatureCollection, a Feature, or a URL string. A choropleth is `{ property, stops: [value, tone][] }`, with at least two stops. Without a choropleth the layer uses `fillTone`. The default fill opacity is `0.6` for a choropleth and `0.25` otherwise. The server does not validate GeoJSON.
|
|
91
|
+
|
|
92
|
+
**Clusters.** Grouped points: `{ id, points: [{ id, lngLat, properties? }], tone?, clusterRadius?, clusterMaxZoom? }`. Cluster bubbles use the group tone, `chart-2` and `chart-3`.
|
|
93
|
+
|
|
94
|
+
**Heatmaps.** `{ id, points: [{ lngLat, weight? }], ramp?, radius?, intensity?, opacity?, maxWeight?, showPointsAtZoom? }`. Weights default to `1` and must be finite and at least `0`. `maxWeight` is the weight that reaches full intensity; it defaults to the largest weight. The `ramp` runs from low to high density and defaults to `['chart-3', 'chart-4', 'primary', 'destructive']`, so it follows the palette and light or dark mode. `radius` (default `20` px at zoom 9) scales with zoom. Set `showPointsAtZoom` to fade the heatmap into individual circles when zoomed in. Replace the data with `setHeatmaps(layers)`; the client updates the source in place.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
const heat = ui.map({ projection: 'globe', heatmaps: [{ id: 'quakes', points, maxWeight: 7, showPointsAtZoom: 6 }] });
|
|
98
|
+
heat.setHeatmaps([{ id: 'quakes', points: points.filter((p) => (p.weight ?? 0) >= 5) }]);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Standalone popup.** `showPopup([lng, lat], { title?, description? })` opens one, and `closePopup()` closes it. The `popup` prop does the same declaratively. Only marker popups take `meta` rows.
|
|
102
|
+
|
|
103
|
+
## Camera
|
|
104
|
+
|
|
105
|
+
| Method | Behaviour |
|
|
106
|
+
|--------|-----------|
|
|
107
|
+
| `flyTo(center, zoom?, { duration? })` | Animates. Keeps the current zoom when `zoom` is omitted |
|
|
108
|
+
| `fitBounds([sw, ne], { padding? })` | Fits the given bounds |
|
|
109
|
+
| `fitBounds('markers', { padding? })` | Fits all current markers. Does nothing when there are none |
|
|
110
|
+
| `jumpTo(center, zoom?)` | Moves without animation |
|
|
111
|
+
| `setProjection('mercator' \| 'globe')` | Sends a new `projection` |
|
|
112
|
+
|
|
113
|
+
Each camera call runs once on the client. The server attaches a nonce, so repeating a call moves the map again.
|
|
114
|
+
|
|
115
|
+
The client reads `center`, `zoom`, `pitch`, `bearing` and `interactive` once, at mount. Changing those props later does not move an existing map. Use the camera methods instead.
|
|
116
|
+
|
|
117
|
+
## Events
|
|
118
|
+
|
|
119
|
+
Handlers are registered only when you pass them. Each handler runs on the server, and receives:
|
|
120
|
+
|
|
121
|
+
| Handler | Fires when | Argument |
|
|
122
|
+
|---------|------------|----------|
|
|
123
|
+
| `onMarkerClick(marker)` | A marker is clicked | The current marker datum |
|
|
124
|
+
| `onMarkerDragEnd(marker, lngLat)` | A draggable marker is dropped | The updated marker and its new `lngLat`. The stored position is updated first |
|
|
125
|
+
| `onRouteClick(route)` | A route is clicked | The current route datum |
|
|
126
|
+
| `onArcClick(arc, group)` | An arc is clicked | The arc and its group |
|
|
127
|
+
| `onFeatureClick(feature)` | A GeoJSON feature is clicked | `{ layerId, featureId?, properties }` |
|
|
128
|
+
| `onClusterPointClick(point, group)` | A clustered point is clicked | The point and its group |
|
|
129
|
+
| `onMapClick(lngLat)` | The map is clicked | `[lng, lat]` |
|
|
130
|
+
| `onViewportChange(viewport)` | The camera stops moving (`moveend`) | `{ center, zoom, bearing, pitch }` |
|
|
131
|
+
| `onLocate(lngLat)` | The locate control is used (needs `controls.locate`) | `[lng, lat]` |
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
`markerDragEnd` is always recorded on the server, even without `onMarkerDragEnd`, so a later `fitBounds('markers')` uses the dropped position.
|
|
135
|
+
|
|
136
|
+
## Live data & in-place updates
|
|
137
|
+
|
|
138
|
+
The map owns its marker, route, arc, cluster and popup state. Each setter sends only the prop it changes, so the client patches the map instead of rebuilding it. Markers are keyed by `id`: when a marker's `lngLat` changes, it moves, and its DOM node stays.
|
|
139
|
+
|
|
140
|
+
| Method | What it does |
|
|
141
|
+
|--------|--------------|
|
|
142
|
+
| `setMarkers(list)` | Replaces all markers |
|
|
143
|
+
| `upsertMarker(marker)` | Inserts, or replaces the marker with the same id |
|
|
144
|
+
| `removeMarker(id)` | Removes a marker. No-op if absent |
|
|
145
|
+
| `setRoutes(list)` / `updateRoute(id, patch)` | Replaces all routes, or merges `patch` into one. The id cannot change |
|
|
146
|
+
| `setArcs(groups)` / `setGeoJSON(layers)` / `setClusters(groups)` / `setHeatmaps(layers)` | Replace that layer type |
|
|
147
|
+
|
|
148
|
+
A courier moving along a route:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
ui.timer(0.25, () => {
|
|
152
|
+
progress = Math.min(1, progress + 0.006);
|
|
153
|
+
map.updateRoute('delivery-route', { progress });
|
|
154
|
+
map.upsertMarker({ id: 'courier', lngLat: pointAt(progress), tone: 'chart-2', icon: 'truck' });
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Validation** runs when you create the map or call a setter, before anything is sent. It throws on:
|
|
159
|
+
|
|
160
|
+
- a missing or empty id, or a duplicate id within its list (markers, routes, arc groups, arcs in a group, cluster groups, points in a group). The message is `duplicate id "…"`.
|
|
161
|
+
- a coordinate that is not `[lng, lat]` with two finite numbers. This covers markers, cluster points, route coordinates, arc endpoints, popups, `flyTo` and `jumpTo` centres, and `fitBounds` corners.
|
|
162
|
+
|
|
163
|
+
## Layout
|
|
164
|
+
|
|
165
|
+
`height` gives a fixed-size map in normal flow:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
ui.card({ title: 'Stores', className: 'p-0 overflow-hidden' }, () => {
|
|
169
|
+
ui.map({ height: '26rem', center: [-6, 40], zoom: 5 });
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`fill: true` makes the map fill its parent (`h-full min-h-0 flex-1`), so the parent needs a definite height. On a page that fills the window, put `fillViewport` on the outer column and `grow` on the panel that holds the map:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
ui.column({ fillViewport: true, gap: 4 }, () => {
|
|
177
|
+
ui.row({ grow: true, wrap: false, gap: 4 }, () => {
|
|
178
|
+
ui.card({ title: 'Stores', scroll: true, className: 'w-80 shrink-0' }, () => { /* list */ });
|
|
179
|
+
ui.column({ grow: true }, () => {
|
|
180
|
+
ui.map({ fill: true, center: [-6, 40], zoom: 5 });
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
See [API reference: Filling the page](./api.md#filling-the-page).
|
|
187
|
+
|
|
188
|
+
## Tiles
|
|
189
|
+
|
|
190
|
+
By default the basemap uses CARTO's hosted styles: Positron for light and Dark Matter for dark, served from `basemaps.cartocdn.com`. Those styles are built on OpenStreetMap data. CARTO's free tier and terms apply, and OpenStreetMap data requires attribution.
|
|
191
|
+
|
|
192
|
+
Before production, check CARTO's current terms and attribution rules, or pass your own styles. Any MapLibre style URL works:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
ui.map({ styles: { light: 'https://your-host.example/light.json', dark: 'https://your-host.example/dark.json' } });
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
[OpenFreeMap](https://openfreemap.org) publishes free style URLs. Check its terms before you rely on it.
|
|
199
|
+
|
|
200
|
+
## Demos
|
|
201
|
+
|
|
202
|
+
All five are under the **Maps** group in the sidebar.
|
|
203
|
+
|
|
204
|
+
- [`/examples/maps`](../apps/demo/src/examples/MapsGallery.ts): markers and popups, routes, arcs, a choropleth, clusters, a seismic heatmap on a globe, and a compact analytics map.
|
|
205
|
+
- [`/examples/delivery-tracker`](../apps/demo/src/examples/DeliveryTracker.ts): a courier moving along a route, with `updateRoute` and `upsertMarker` on a timer.
|
|
206
|
+
- [`/examples/store-locator`](../apps/demo/src/examples/StoreLocator.ts): a filterable store list where list rows and markers select each other.
|
|
207
|
+
- [`/examples/globe`](../apps/demo/src/examples/CommunityGlobe.ts): a globe with arcs and markers that refresh every 30 seconds.
|
|
208
|
+
- [`/examples/uptime`](../apps/demo/src/examples/UptimeMonitor.ts): a status globe of edge locations, refreshed every 2 seconds.
|
|
209
|
+
|
|
210
|
+
## See also
|
|
211
|
+
|
|
212
|
+
- [Updating in place](./updating-in-place.md): the general rules for patching instead of rebuilding
|
|
213
|
+
- [API reference](./api.md#uimapprops): `ui.map` in the Layout section
|
|
214
|
+
- [Examples](./examples.md): the demo catalog
|
package/docs/protocol.md
CHANGED
|
@@ -79,6 +79,7 @@ Browser Back/Forward (or other `popstate`) changed the query and/or hash **witho
|
|
|
79
79
|
| `replace` | `id`, `node` | Replace entire subtree at `id` |
|
|
80
80
|
| `updateProps` | `id`, `props` | Shallow-merge props |
|
|
81
81
|
| `setChildren` | `id`, `children` | Replace children array |
|
|
82
|
+
| `appendRows` | `id`, `prop`, `rows`, `max?` | Append `rows` to the array prop `prop`, then keep the last `max` items (chart streaming) |
|
|
82
83
|
| `remove` | `id` | Remove node from parent |
|
|
83
84
|
|
|
84
85
|
### `navigate`
|
|
@@ -20,6 +20,7 @@ The flicker is caused by the remount, not by the new value. If a frame carries o
|
|
|
20
20
|
| Change one value (a label, badge, or input) | `label.setText(…)`, `el.setValue(…)`, `el.update({ … })` | `updateProps` for that element |
|
|
21
21
|
| Live table data | `table.setRows(rows)` on a table created with a stable `keyField` (add `highlightChanges: true` to flash changed rows) | `updateProps` on the table; rows keyed by `keyField` keep their DOM |
|
|
22
22
|
| Table refetch | `table.setLoading(true)` / `table.withLoading(fn)` | `updateProps` (`loading`); previous rows stay, dimmed, instead of blanking |
|
|
23
|
+
| Live chart data (any chart) | `chart.appendData(point, { max })` for a stream, `chart.setData(rows)` for a snapshot, `chart.upsertData(row)` for a growing bucket | `appendRows` with only the new points, or `updateProps` (`data`); the chart stays mounted |
|
|
23
24
|
| A section whose structure depends on data | `ui.refreshable(() => …)` + `.refresh()`, or `ui.auto(() => …)` | Reconciled: matched children get `updateProps` only; added, removed, or reordered children cause one `setChildren` per affected parent |
|
|
24
25
|
| Items in a list inside either of the above | `.key(item.id)` on each item | Items keep their identity through reorder, filter, and prepend |
|
|
25
26
|
|
|
@@ -31,7 +32,7 @@ When a `refreshable` or `auto` block re-runs, Clay builds a fresh tree and match
|
|
|
31
32
|
|
|
32
33
|
1. **Keyed first.** A new child with `.key(k)` matches the old child with the same key and the same element type. Keyed children are matched only by key.
|
|
33
34
|
2. **Unkeyed by position.** Unkeyed children are matched in order, by element type. If the next old child matches the current new one, the current old child was removed and is skipped. If the current old child matches the next new one, the new child was inserted. Otherwise the pair is treated as a replacement at that position. This lookahead covers a single insert or remove; larger reshuffles should be keyed.
|
|
34
|
-
3. **Matched pairs keep the
|
|
35
|
+
3. **Matched pairs keep the id on screen.** The freshly built element takes over the old element's id and the old one is destroyed. Only props that actually changed are sent, as one `updateProps`. Because the fresh instance is the one that stays live, its built-in handlers (table search, sort and paging, map drags) and any reference you saved while building (`table = ui.dataTable(...)`) keep working after the refresh.
|
|
35
36
|
4. **Leftovers.** Old children with no match are destroyed. New children with no match are mounted. The parent sends one `setChildren` only if its list of ids changed. A child whose type changed at a position is replaced; its siblings keep their ids.
|
|
36
37
|
5. **Nested blocks keep their instance.** A nested `refreshable` or `auto` keeps its id and its subscriptions and adopts the new builder, so its own refreshes keep working.
|
|
37
38
|
|
|
@@ -103,11 +104,35 @@ async function refresh() {
|
|
|
103
104
|
|
|
104
105
|
If you want columns to stay fixed across refreshes, pass `columns` explicitly. Otherwise columns are inferred again on each `setRows`.
|
|
105
106
|
|
|
107
|
+
### Live charts (`appendData` / `setData` / `upsertData`)
|
|
108
|
+
|
|
109
|
+
Every chart (`ui.lineChart`, `areaChart`, `barChart`, `composedChart`, `scatterChart`, `pieChart`, `radarChart`, `radialChart`, `sparkline`, and the `ui.chart.*` builders) returns a `ChartElement`. Create it once and push data into it.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const latency = ui.lineChart({ data: seed, xKey: 't', series: [{ key: 'p50', label: 'p50' }] });
|
|
113
|
+
const byRegion = ui.barChart({ data: regions, xKey: 'region', series: [{ key: 'orders', label: 'Orders' }] });
|
|
114
|
+
|
|
115
|
+
ui.timer(1, () => {
|
|
116
|
+
latency.appendData({ t: clock(), p50: sample() }, { max: 60 }); // only the new point is sent
|
|
117
|
+
latency.setHeadline({ value: `${p50} ms` });
|
|
118
|
+
});
|
|
119
|
+
ui.timer(5, async () => byRegion.setData(await fetchRegions())); // bars tween to the new values
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- **`appendData(rows, { max })`** sends only the new rows. The client appends them and drops the oldest past `max`, so a sliding window costs one point per tick.
|
|
123
|
+
- **`setData(rows)`** replaces the data. When the row count and first x value are unchanged, the marks tween to their new values.
|
|
124
|
+
- **`upsertData(row, { key, max })`** replaces the row with the same x value (or `nameKey` for pies), or appends it. Use it for a bucket that keeps growing, such as the current minute.
|
|
125
|
+
- **`setSeries`, `setHeadline`, `setTitle`, `setDescription`, `setLoading`** patch one prop each. Anything else goes through `update({ … })`.
|
|
126
|
+
|
|
127
|
+
KPI stat grids work the same way: `ui.stat(items)` returns a `StatElement`. `updateItem(titleOrIndex, patch)` changes one card, `appendSparkline(titleOrIndex, points, { max })` extends that card's sparkline, and `setItems(items)` replaces them all.
|
|
128
|
+
|
|
129
|
+
Appends and sliding windows draw without animation, so lines don't slide sideways or replay their entrance. Pass `animate: false` to turn chart animation off entirely. The interactive period tabs keep the selected period while data streams in.
|
|
130
|
+
|
|
106
131
|
## Anti-patterns
|
|
107
132
|
|
|
108
133
|
- **Rebuilding a whole section when one value changes.** Wrapping a whole page in `ui.refreshable` and calling `.refresh()` every second to update a clock. Keep a reference to the clock label and call `setText`.
|
|
109
134
|
- **Unkeyed lists that get sorted or filtered.** Without `.key()`, position is the identity. After a sort, the element in slot 3 is reused for whatever item is now third, so focus and typed text stay in the slot instead of following the row.
|
|
110
|
-
- **Creating a new table instead of `setRows`.** A `ui.dataTable(...)`
|
|
135
|
+
- **Creating a new table instead of `setRows`.** A `ui.dataTable(...)` rebuilt inside a refreshable keeps its id and stays interactive, but each rebuild starts the table's server-side state over, so sort, search, page and selection reset. Create the table once and call `setRows`.
|
|
111
136
|
- **Tables without `keyField`.** Rows are stamped `__rowId` by their index, so inserting or re-sorting the source shifts every row's identity. Set `keyField` to your primary key.
|
|
112
137
|
- **Toggling a constructor option by rebuilding.** Recreating a table with `zebra: on` inside an `auto` block. Use the setters instead: `setZebra`, `setDensity`, `setHighlightChanges`.
|
|
113
138
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@close-by/clay",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.20",
|
|
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.
|
|
41
|
-
"@close-by/clay-core": "0.2.
|
|
42
|
-
"@close-by/clay-components": "0.2.
|
|
43
|
-
"@close-by/clay-server": "0.2.
|
|
40
|
+
"@close-by/clay-compiler": "0.2.20",
|
|
41
|
+
"@close-by/clay-core": "0.2.20",
|
|
42
|
+
"@close-by/clay-components": "0.2.20",
|
|
43
|
+
"@close-by/clay-server": "0.2.20",
|
|
44
44
|
"@tailwindcss/cli": "^4.3.3",
|
|
45
45
|
"tailwindcss": "^4.3.3"
|
|
46
46
|
}
|
package/src/index.ts
CHANGED
|
@@ -113,6 +113,9 @@ import {
|
|
|
113
113
|
list as listFactory,
|
|
114
114
|
imageCrop as imageCropFactory,
|
|
115
115
|
gantt as ganttFactory,
|
|
116
|
+
map as mapFactory,
|
|
117
|
+
type MapElement,
|
|
118
|
+
type MapProps,
|
|
116
119
|
chart as chartNamespace,
|
|
117
120
|
table as tableFactory,
|
|
118
121
|
flow as flowFactory,
|
|
@@ -200,6 +203,8 @@ import {
|
|
|
200
203
|
type CheckboxGroupProps,
|
|
201
204
|
type CheckboxGroupOption,
|
|
202
205
|
type StatItem,
|
|
206
|
+
type StatItemRef,
|
|
207
|
+
type StatElement,
|
|
203
208
|
type DataTableProps,
|
|
204
209
|
type DataTableAction,
|
|
205
210
|
type DataTableView,
|
|
@@ -232,6 +237,7 @@ import {
|
|
|
232
237
|
type ComposedChartProps,
|
|
233
238
|
type ComposedChartSeries,
|
|
234
239
|
type ComposedSeriesType,
|
|
240
|
+
type ChartElement,
|
|
235
241
|
type CartesianChartProps,
|
|
236
242
|
type ChartSeries,
|
|
237
243
|
type ChartHeadline,
|
|
@@ -346,6 +352,14 @@ import {
|
|
|
346
352
|
type GanttDependency,
|
|
347
353
|
type GanttRange,
|
|
348
354
|
type GanttItemMovePayload,
|
|
355
|
+
type MapMarkerDatum,
|
|
356
|
+
type MapRouteDatum,
|
|
357
|
+
type MapArcGroup,
|
|
358
|
+
type MapGeoJSONLayer,
|
|
359
|
+
type MapClusterGroup,
|
|
360
|
+
type MapHeatmapLayer,
|
|
361
|
+
type LngLat,
|
|
362
|
+
type MapTone,
|
|
349
363
|
type FlowProps,
|
|
350
364
|
type FlowNodeProps,
|
|
351
365
|
type FlowNodeKind,
|
|
@@ -412,7 +426,6 @@ import {
|
|
|
412
426
|
DialogElement,
|
|
413
427
|
DialogStackElement,
|
|
414
428
|
StepperElement,
|
|
415
|
-
NavigationMenuElement,
|
|
416
429
|
AlertDialogElement,
|
|
417
430
|
DropdownMenuElement,
|
|
418
431
|
ContextMenuElement,
|
|
@@ -455,6 +468,7 @@ import {
|
|
|
455
468
|
timer as timerCore,
|
|
456
469
|
storage as storageCore,
|
|
457
470
|
theme as themeCore,
|
|
471
|
+
sound as soundCore,
|
|
458
472
|
reactive as reactiveCore,
|
|
459
473
|
state as stateCore,
|
|
460
474
|
subscribe as subscribeCore,
|
|
@@ -515,6 +529,9 @@ export {
|
|
|
515
529
|
} from './tailwind';
|
|
516
530
|
|
|
517
531
|
|
|
532
|
+
export { ChartElement, StatElement } from '@close-by/clay-components';
|
|
533
|
+
export type { StatItemRef } from '@close-by/clay-components';
|
|
534
|
+
|
|
518
535
|
export type {
|
|
519
536
|
GridProps,
|
|
520
537
|
LayoutProps,
|
|
@@ -642,6 +659,15 @@ export type {
|
|
|
642
659
|
GanttDependency,
|
|
643
660
|
GanttRange,
|
|
644
661
|
GanttItemMovePayload,
|
|
662
|
+
MapProps,
|
|
663
|
+
MapMarkerDatum,
|
|
664
|
+
MapRouteDatum,
|
|
665
|
+
MapArcGroup,
|
|
666
|
+
MapGeoJSONLayer,
|
|
667
|
+
MapClusterGroup,
|
|
668
|
+
MapHeatmapLayer,
|
|
669
|
+
LngLat,
|
|
670
|
+
MapTone,
|
|
645
671
|
FlowProps,
|
|
646
672
|
FlowNodeProps,
|
|
647
673
|
FlowNodeKind,
|
|
@@ -1180,6 +1206,15 @@ export function gantt(props?: GanttProps): GanttElement {
|
|
|
1180
1206
|
return ganttFactory(props);
|
|
1181
1207
|
}
|
|
1182
1208
|
|
|
1209
|
+
/**
|
|
1210
|
+
* Live map (MapLibre, styled to the Clay theme) with markers, routes, arcs,
|
|
1211
|
+
* GeoJSON layers, clusters and a popup. Owns its marker / route / popup /
|
|
1212
|
+
* camera state; setters update in place. See {@link MapProps}.
|
|
1213
|
+
*/
|
|
1214
|
+
export function map(props?: MapProps): MapElement {
|
|
1215
|
+
return mapFactory(props);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1183
1218
|
/**
|
|
1184
1219
|
* Interactive flow diagram (`@xyflow/react`). Owns edges + positions;
|
|
1185
1220
|
* node bodies are Clay trees; drag by card chrome (controls are nodrag);
|
|
@@ -1193,7 +1228,7 @@ export function flow(a: any, b?: any): FlowElement {
|
|
|
1193
1228
|
}
|
|
1194
1229
|
|
|
1195
1230
|
/** Stat strip (label/value items). Prefer `ui.stat`. */
|
|
1196
|
-
export function stat(items: StatItem[], props?: { className?: string }):
|
|
1231
|
+
export function stat(items: StatItem[], props?: { className?: string }): StatElement {
|
|
1197
1232
|
return statFactory(items, props);
|
|
1198
1233
|
}
|
|
1199
1234
|
|
|
@@ -1217,47 +1252,47 @@ export const chart = chartNamespace;
|
|
|
1217
1252
|
export const ai = aiNamespace;
|
|
1218
1253
|
|
|
1219
1254
|
/** Area chart. Prefer `ui.areaChart`. See {@link AreaChartProps}. */
|
|
1220
|
-
export function areaChart(props: AreaChartProps):
|
|
1255
|
+
export function areaChart(props: AreaChartProps): ChartElement {
|
|
1221
1256
|
return areaChartFactory(props);
|
|
1222
1257
|
}
|
|
1223
1258
|
|
|
1224
1259
|
/** Bar chart. Prefer `ui.barChart`. See {@link BarChartProps}. */
|
|
1225
|
-
export function barChart(props: BarChartProps):
|
|
1260
|
+
export function barChart(props: BarChartProps): ChartElement {
|
|
1226
1261
|
return barChartFactory(props);
|
|
1227
1262
|
}
|
|
1228
1263
|
|
|
1229
1264
|
/** Line chart. Prefer `ui.lineChart`. See {@link LineChartProps}. */
|
|
1230
|
-
export function lineChart(props: LineChartProps):
|
|
1265
|
+
export function lineChart(props: LineChartProps): ChartElement {
|
|
1231
1266
|
return lineChartFactory(props);
|
|
1232
1267
|
}
|
|
1233
1268
|
|
|
1234
1269
|
/** Pie / donut chart. Prefer `ui.pieChart`. See {@link PieChartProps}. */
|
|
1235
|
-
export function pieChart(props: PieChartProps):
|
|
1270
|
+
export function pieChart(props: PieChartProps): ChartElement {
|
|
1236
1271
|
return pieChartFactory(props);
|
|
1237
1272
|
}
|
|
1238
1273
|
|
|
1239
1274
|
/** Radar chart. Prefer `ui.radarChart`. See {@link RadarChartProps}. */
|
|
1240
|
-
export function radarChart(props: RadarChartProps):
|
|
1275
|
+
export function radarChart(props: RadarChartProps): ChartElement {
|
|
1241
1276
|
return radarChartFactory(props);
|
|
1242
1277
|
}
|
|
1243
1278
|
|
|
1244
1279
|
/** Radial / gauge chart. Prefer `ui.radialChart`. See {@link RadialChartProps}. */
|
|
1245
|
-
export function radialChart(props: RadialChartProps):
|
|
1280
|
+
export function radialChart(props: RadialChartProps): ChartElement {
|
|
1246
1281
|
return radialChartFactory(props);
|
|
1247
1282
|
}
|
|
1248
1283
|
|
|
1249
1284
|
/** Scatter chart. Prefer `ui.scatterChart`. See {@link ScatterChartProps}. */
|
|
1250
|
-
export function scatterChart(props: ScatterChartProps):
|
|
1285
|
+
export function scatterChart(props: ScatterChartProps): ChartElement {
|
|
1251
1286
|
return scatterChartFactory(props);
|
|
1252
1287
|
}
|
|
1253
1288
|
|
|
1254
1289
|
/** Composed multi-geometry chart. Prefer `ui.composedChart`. See {@link ComposedChartProps}. */
|
|
1255
|
-
export function composedChart(props: ComposedChartProps):
|
|
1290
|
+
export function composedChart(props: ComposedChartProps): ChartElement {
|
|
1256
1291
|
return composedChartFactory(props);
|
|
1257
1292
|
}
|
|
1258
1293
|
|
|
1259
1294
|
/** KPI card with inline sparkline. Prefer `ui.sparkline`. See {@link SparklineProps}. */
|
|
1260
|
-
export function sparkline(props: SparklineProps):
|
|
1295
|
+
export function sparkline(props: SparklineProps): ChartElement {
|
|
1261
1296
|
return sparklineFactory(props);
|
|
1262
1297
|
}
|
|
1263
1298
|
|
|
@@ -1677,6 +1712,9 @@ export const storage = storageCore;
|
|
|
1677
1712
|
/** Server-driven appearance (`light` | `dark` | `system`). */
|
|
1678
1713
|
export const theme = themeCore;
|
|
1679
1714
|
|
|
1715
|
+
/** Interface sound cues: `ui.sound.set(on)`, `ui.sound.volume(0..1)`, `ui.sound.play(name)`. */
|
|
1716
|
+
export const sound = soundCore;
|
|
1717
|
+
|
|
1680
1718
|
/** Proxy that notifies subscribers on property change. Prefer this over importing from `@close-by/clay-core`. */
|
|
1681
1719
|
export const reactive = reactiveCore;
|
|
1682
1720
|
|
|
@@ -1924,6 +1962,7 @@ export const ui = {
|
|
|
1924
1962
|
list,
|
|
1925
1963
|
imageCrop,
|
|
1926
1964
|
gantt,
|
|
1965
|
+
map,
|
|
1927
1966
|
flow,
|
|
1928
1967
|
stat,
|
|
1929
1968
|
dataTable,
|
|
@@ -1991,6 +2030,7 @@ export const ui = {
|
|
|
1991
2030
|
timer,
|
|
1992
2031
|
storage,
|
|
1993
2032
|
theme,
|
|
2033
|
+
sound,
|
|
1994
2034
|
reactive,
|
|
1995
2035
|
state,
|
|
1996
2036
|
urlState,
|