@close-by/clay 0.2.10 → 0.2.11
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 +1 -1
- package/docs/SKILL.md +1 -1
- package/docs/api.md +7 -3
- package/docs/boot.md +5 -1
- package/docs/elements.md +1 -1
- package/docs/examples.md +1 -0
- package/docs/ops-patterns.md +29 -0
- package/docs/reactive-let.md +7 -6
- package/package.json +5 -5
- package/src/index.ts +2 -1
- package/src/pages.ts +20 -1
package/SKILL.md
CHANGED
|
@@ -116,7 +116,7 @@ Resolve docs programmatically: `resolveClayDocsDir()` from `@close-by/clay/docs-
|
|
|
116
116
|
|
|
117
117
|
## Optional compile-time reactive `let`
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
Auto-registers when pages use `// @clay-reactive` / `"use reactive"` (`clay` / `loadPages` default). Prefer explicit `ui.state` + `ui.auto` for production/async-heavy apps; force with `--reactive-let` if needed.
|
|
120
120
|
|
|
121
121
|
## Package map (monorepo)
|
|
122
122
|
|
package/docs/SKILL.md
CHANGED
|
@@ -116,7 +116,7 @@ Resolve docs programmatically: `resolveClayDocsDir()` from `@close-by/clay/docs-
|
|
|
116
116
|
|
|
117
117
|
## Optional compile-time reactive `let`
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
Auto-registers when pages use `// @clay-reactive` / `"use reactive"` (`clay` / `loadPages` default). Prefer explicit `ui.state` + `ui.auto` for production/async-heavy apps; force with `--reactive-let` if needed.
|
|
120
120
|
|
|
121
121
|
## Package map (monorepo)
|
|
122
122
|
|
package/docs/api.md
CHANGED
|
@@ -1682,17 +1682,18 @@ ui.scrollArea(
|
|
|
1682
1682
|
|
|
1683
1683
|
#### `ui.viewportEnter(fn, props?)` / `ui.viewportEnter(props, fn)`
|
|
1684
1684
|
|
|
1685
|
-
IntersectionObserver wrapper. Fires `onEnter` when the element intersects the
|
|
1685
|
+
IntersectionObserver wrapper. Fires `onEnter` when the element intersects the scroll root — default **once**, root = browser viewport.
|
|
1686
1686
|
|
|
1687
1687
|
```typescript
|
|
1688
1688
|
ui.viewportEnter(
|
|
1689
1689
|
{
|
|
1690
1690
|
once: true,
|
|
1691
|
+
root: 'nearest-scroll', // overflow panel / scroll-area, not only the window
|
|
1691
1692
|
rootMargin: '80px',
|
|
1692
|
-
onEnter: () => void
|
|
1693
|
+
onEnter: () => void loadMore(),
|
|
1693
1694
|
},
|
|
1694
1695
|
() => {
|
|
1695
|
-
ui.label('
|
|
1696
|
+
ui.label('Load more…');
|
|
1696
1697
|
},
|
|
1697
1698
|
);
|
|
1698
1699
|
```
|
|
@@ -1702,9 +1703,12 @@ ui.viewportEnter(
|
|
|
1702
1703
|
| `className` | `string` | |
|
|
1703
1704
|
| `onEnter` | `() => void \| Promise<void>` | — client emits `enter` |
|
|
1704
1705
|
| `once` | `boolean` | `true` |
|
|
1706
|
+
| `root` | `'viewport' \| 'nearest-scroll' \| string` | `'viewport'` |
|
|
1705
1707
|
| `rootMargin` | `string` | `'0px'` |
|
|
1706
1708
|
| `threshold` | `number \| number[]` | `0` |
|
|
1707
1709
|
|
|
1710
|
+
`nearest-scroll` walks ancestors for overflow scroll or `[data-slot="scroll-area-viewport"]`. A CSS selector uses `closest` / `querySelector`.
|
|
1711
|
+
|
|
1708
1712
|
#### `ui.keybind(props)`
|
|
1709
1713
|
|
|
1710
1714
|
Headless keyboard chord listener. Renders nothing; while the node is in the tree the client listens on `window` `keydown`, matches a chord, and emits `press` → `onPress`.
|
package/docs/boot.md
CHANGED
|
@@ -90,7 +90,11 @@ Legacy `_clay-reload-*.ts` files under `pages/` (pre-0.2.5) should be deleted; m
|
|
|
90
90
|
|
|
91
91
|
## Reactive-let
|
|
92
92
|
|
|
93
|
-
The CLI
|
|
93
|
+
The CLI **auto-registers** the compile-time `let` transform when any loaded page contains `// @clay-reactive` or `"use reactive"`. Force on with `--reactive-let`, force off with `--no-reactive-let`.
|
|
94
|
+
|
|
95
|
+
`ui.loadPages` does the same scan-before-import in library mode (default `reactiveLet: 'auto'`), so apps no longer need a manual `registerReactiveLetPlugin()` before `loadPages`. Prefer Phase 1 `ui.state` / `ui.auto` for dense/async production apps until you are ready for the pragma path — see [reactive-let](./reactive-let.md).
|
|
96
|
+
|
|
97
|
+
`configureRun` Tailwind options (`appendCss`, custom `content`, …) are **merged** with CLI defaults; the CLI no longer replaces the whole `tailwind` object.
|
|
94
98
|
|
|
95
99
|
## Checklist (avoid drift)
|
|
96
100
|
|
package/docs/elements.md
CHANGED
|
@@ -91,7 +91,7 @@ Each factory creates an `Element` with a wire `type` string. The React client ma
|
|
|
91
91
|
| `command` | `command` (+ `commandgroup` / `commanditem` / `commandseparator`) | Command palette (`mode: 'dialog' \| 'inline'`; dialog owns `open`) |
|
|
92
92
|
| `resizable` | `resizable` (+ `resizablepanel` / `resizablehandle`) | `react-resizable-panels` |
|
|
93
93
|
| `scrollArea` | `scrollarea` | ShadCN `ScrollArea` (`onNearEnd` for infinite scroll) |
|
|
94
|
-
| `viewportEnter` | `viewportEnter` | IntersectionObserver
|
|
94
|
+
| `viewportEnter` | `viewportEnter` | IntersectionObserver (`onEnter`, `once?`, `root?`) |
|
|
95
95
|
| `keybind` | `keybind` | Headless `window` `keydown` chord listener (`return null`) |
|
|
96
96
|
| `kbd` | `kbd` | Display-only chord glyphs (`Kbd` / `KbdGroup`; same tokens as `keybind`) |
|
|
97
97
|
| `markdown` | `markdown` | Client `marked` + DOMPurify |
|
package/docs/examples.md
CHANGED
|
@@ -18,6 +18,7 @@ After `bun run build:client && bun run demo` (or `bun run demo:cli`), open http:
|
|
|
18
18
|
| `/examples/dashboard` | `Dashboard.ts` | `stat`, `areaChart`, full-chrome `dataTable` (views, editors, detail drawer) |
|
|
19
19
|
| `/examples/datatable` | `DataTableDemo.ts` | Multi-sort (Shift+click / badges), footer aggregates + a11y hints, column pin, remote filter/sort + pagination; density/zebra, editors (Enter/Esc + focus restore), resize, virtualization (≥40, coexists with reorder via windowed drop targets); facets, bulk, collapse/expand, loading/empty; `ui.table` sugar |
|
|
20
20
|
| `/examples/composed` | `ComposedDemo.ts` | ReUI batch 1: `ui.timeline`, `ui.stepper`, `ui.dateRange`, `ui.feedList`/`ui.feedRow`, `ui.sparkline` |
|
|
21
|
+
| `/examples/ops-console` | `OpsConsoleDemo.ts` | Dogfood ops console: `filterBar` → `feedList` → `descriptionList` / `staticTable` (batches 1–7 in one surface) |
|
|
21
22
|
| `/examples/reui-batches` | `ReuiBatchesDemo.ts` | ReUI batches 3–6: `buttonGroup`, `empty`, `pagination`, `filterBar`, form inputs, `navigationMenu`, `notice`, `stat`+sparkline, `eventCalendar`, datatable `showFilterChips` |
|
|
22
23
|
| `/examples/reui-batch-7` | `ReuiBatch7Demo.ts` | ReUI batch 7: `inputGroup`, `toggle`, `descriptionList`, `staticTable`, `aspectRatio`, `itemList`, `checkboxGroup` |
|
|
23
24
|
| `/examples/charts` | `ChartDemo.ts` | `ui.chart.*`, `ui.sparkline`, composed/scatter charts |
|
package/docs/ops-patterns.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Dense internal tools (live feeds, master–detail, logs/traces) push Clay past card dashboards. Prefer Phase 1 reactivity ([Concepts](./concepts.md#canonical-recipe-state--auto--timer)) and the helpers below.
|
|
4
4
|
|
|
5
|
+
**Runnable dogfood:** `/examples/ops-console` (`OpsConsoleDemo.ts`) — one Orders-shaped surface using `filterBar`, `inputGroup`, `feedList`/`feedRow`, `descriptionList`, `staticTable`, `pagination`, `empty`, `notice`, and `toggle`. Use that page to find API friction before adding more wire types.
|
|
6
|
+
|
|
5
7
|
## Status chips
|
|
6
8
|
|
|
7
9
|
```ts
|
|
@@ -154,6 +156,33 @@ ui.auto(() => {
|
|
|
154
156
|
|
|
155
157
|
For **`ui.dataTable`**, pass `showFilterChips: true` to surface active search + column filters as removable chips in the table chrome (`views` remain the saved-view tabs API).
|
|
156
158
|
|
|
159
|
+
## Infinite scroll / load-more
|
|
160
|
+
|
|
161
|
+
**Blessed pattern for overflow feeds** (master–detail panels): `ui.viewportEnter` with `root: 'nearest-scroll'`. Remount the sentinel while a page is in flight if you need to re-arm after `once: true`:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
ui.auto(() => {
|
|
165
|
+
if (live.loadingMore) {
|
|
166
|
+
ui.label('Loading…').classes('text-xs text-muted-foreground p-2');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (!live.hasMore) return;
|
|
170
|
+
ui.viewportEnter(
|
|
171
|
+
{
|
|
172
|
+
once: true,
|
|
173
|
+
root: 'nearest-scroll',
|
|
174
|
+
rootMargin: '120px',
|
|
175
|
+
onEnter: () => void loadNextPage(),
|
|
176
|
+
},
|
|
177
|
+
() => {
|
|
178
|
+
ui.label(' ').classes('h-1');
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Alternative when you already wrap the list in **`ui.scrollArea`**: use `onNearEnd` on the scroll area (scroll-root is built-in). Prefer one pattern per surface — `viewportEnter` + `nearest-scroll` does **not** require `scrollArea`.
|
|
185
|
+
|
|
157
186
|
## Logs / traces — sensitive bodies
|
|
158
187
|
|
|
159
188
|
Structured logs often contain API keys or partner tokens. Scrubbing is the **app’s** job; Clay can still reduce shoulder-surfing:
|
package/docs/reactive-let.md
CHANGED
|
@@ -75,17 +75,17 @@ Phase 1 remains the **escape hatch** — copy patterns from [`ReactiveLetOrders.
|
|
|
75
75
|
|
|
76
76
|
Package `@close-by/clay-compiler` rewrites a **documented subset** of `let` into Phase 1 APIs.
|
|
77
77
|
|
|
78
|
-
### Opt-in
|
|
78
|
+
### Opt-in
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
1. **CLI / loader:** pass `--reactive-let` to `clay` (or call `registerReactiveLetPlugin()` yourself). Off by default.
|
|
83
|
-
2. **Source:** one of:
|
|
80
|
+
1. **Source:** one of:
|
|
84
81
|
- File pragma: `// @clay-reactive` (near the top of the file) — applies to **`ui.page` / `page` callbacks only**
|
|
85
82
|
- Block directive as the first statement: `"use reactive";` — required for nested helpers / `ui.column(() => …)` lets
|
|
83
|
+
2. **Loader:** by default, `clay` and `ui.loadPages` **auto-register** the Bun plugin when any scanned page has the pragma / directive. Force with `--reactive-let` / `loadPages(dir, { reactiveLet: true })`; disable with `--no-reactive-let` / `{ reactiveLet: false }`. Manual `registerReactiveLetPlugin()` is still fine (and idempotent).
|
|
86
84
|
|
|
87
85
|
`ui.page(...)` alone does **not** opt in. Module-level helpers (`function mergeUnits() { … }`) and nested `function` / arrow bodies do **not** inherit the file pragma — use `"use reactive";` inside the page callback or a nested site when needed.
|
|
88
86
|
|
|
87
|
+
**Hard fail:** a local that shadows lifted state and whose initializer reads that name (`const detail = detail` or `const detail = rows.find(… detail …)`) **refuses to emit** — rename the local. Silent `undefined` after fetch was a hub dogfood failure mode.
|
|
88
|
+
|
|
89
89
|
### Who wraps `ui.auto`?
|
|
90
90
|
|
|
91
91
|
Compile-time region analysis **does not** see into nested function bodies. Authors must still wrap rebuild regions when UI is built from helpers or widget callbacks:
|
|
@@ -111,7 +111,8 @@ Inline UI in widget callbacks (`ui.label(expr)`, `ui.icon(…, { className: expr
|
|
|
111
111
|
| `units.filter(u => { … })` reading lifted state | Plain JS (no `ui.auto` inside filter) | Nothing |
|
|
112
112
|
| `onClick` / `onChange` / `ui.timer` handler | Not a build region | Normal JS |
|
|
113
113
|
| `const live = ui.state({…})` (no pragma) | Unchanged | Explicit `ui.auto` (Phase 1) |
|
|
114
|
-
| Local `const detail = …` shadows lifted `let detail` | Renames in emit + compile warning | Prefer distinct names |
|
|
114
|
+
| Local `const detail = …` shadows lifted `let detail` (no self-read) | Renames in emit + compile warning | Prefer distinct names |
|
|
115
|
+
| Local `const detail = detail` / initializer reads `detail` | **Hard error** — transform refuses to emit | Rename the local |
|
|
115
116
|
|
|
116
117
|
### What transforms
|
|
117
118
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@close-by/clay",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
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.11",
|
|
41
|
+
"@close-by/clay-core": "0.2.11",
|
|
42
|
+
"@close-by/clay-components": "0.2.11",
|
|
43
|
+
"@close-by/clay-server": "0.2.11",
|
|
44
44
|
"@tailwindcss/cli": "^4.3.3",
|
|
45
45
|
"tailwindcss": "^4.3.3"
|
|
46
46
|
}
|
package/src/index.ts
CHANGED
|
@@ -258,6 +258,7 @@ import {
|
|
|
258
258
|
type ResizableHandleProps,
|
|
259
259
|
type ScrollAreaProps,
|
|
260
260
|
type ViewportEnterProps,
|
|
261
|
+
type ViewportEnterRoot,
|
|
261
262
|
type KeybindProps,
|
|
262
263
|
type KbdProps,
|
|
263
264
|
type BreadcrumbItem,
|
|
@@ -284,7 +285,6 @@ import {
|
|
|
284
285
|
type HtmlProps,
|
|
285
286
|
type ImageProps,
|
|
286
287
|
type IframeProps,
|
|
287
|
-
type ViewportEnterProps,
|
|
288
288
|
type UploadProps,
|
|
289
289
|
type UploadVariant,
|
|
290
290
|
type UploadedFile,
|
|
@@ -571,6 +571,7 @@ export type {
|
|
|
571
571
|
ImageProps,
|
|
572
572
|
IframeProps,
|
|
573
573
|
ViewportEnterProps,
|
|
574
|
+
ViewportEnterRoot,
|
|
574
575
|
UploadProps,
|
|
575
576
|
UploadVariant,
|
|
576
577
|
UploadedFile,
|
package/src/pages.ts
CHANGED
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
type PageFn,
|
|
8
8
|
} from '@close-by/clay-core';
|
|
9
9
|
import { warnClayPageIssues } from '@close-by/clay-compiler';
|
|
10
|
+
import {
|
|
11
|
+
ensureReactiveLetPluginForPaths,
|
|
12
|
+
registerReactiveLetPlugin,
|
|
13
|
+
} from '@close-by/clay-compiler/plugin';
|
|
10
14
|
import type { AppNavItem } from '@close-by/clay-components';
|
|
11
15
|
|
|
12
16
|
/**
|
|
@@ -109,11 +113,26 @@ function shouldSkipPageFile(relativePath: string): boolean {
|
|
|
109
113
|
* Clears prior registrations first, then fresh-imports each module (cache-busted)
|
|
110
114
|
* so `--reload` picks up edits. Each module should call `ui.page` once; optional
|
|
111
115
|
* `pageMeta` is attached to the new path.
|
|
116
|
+
*
|
|
117
|
+
* When `reactiveLet` is `'auto'` (default), scans pages for `// @clay-reactive` /
|
|
118
|
+
* `"use reactive"` and registers the Bun plugin **before** imports so library
|
|
119
|
+
* boot does not need a manual `registerReactiveLetPlugin()` call.
|
|
112
120
|
*/
|
|
113
|
-
export async function loadPages(
|
|
121
|
+
export async function loadPages(
|
|
122
|
+
dir: string | URL,
|
|
123
|
+
opts?: { reactiveLet?: boolean | 'auto' },
|
|
124
|
+
): Promise<string[]> {
|
|
114
125
|
const absDir = typeof dir === 'string' ? dir : fileURLToPath(dir);
|
|
115
126
|
const glob = new Bun.Glob('**/*.{ts,tsx}');
|
|
116
127
|
const files = [...glob.scanSync({ cwd: absDir })].filter((f) => !shouldSkipPageFile(f)).sort();
|
|
128
|
+
const fullPaths = files.map((relative) => join(absDir, relative));
|
|
129
|
+
|
|
130
|
+
const reactiveMode = opts?.reactiveLet ?? 'auto';
|
|
131
|
+
if (reactiveMode === true) {
|
|
132
|
+
registerReactiveLetPlugin();
|
|
133
|
+
} else if (reactiveMode === 'auto') {
|
|
134
|
+
await ensureReactiveLetPluginForPaths(fullPaths);
|
|
135
|
+
}
|
|
117
136
|
|
|
118
137
|
clearPages();
|
|
119
138
|
clearPageMeta();
|