@close-by/clay 0.2.10 → 0.2.12
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 +51 -5
- package/docs/reactive-let.md +7 -6
- package/package.json +5 -5
- package/src/index.ts +20 -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
|
|
@@ -37,9 +39,9 @@ const live = ui.state({ selectedId: null as string | null, rows: [] as Row[] });
|
|
|
37
39
|
|
|
38
40
|
ui.auto(() => {
|
|
39
41
|
ui.resizable(
|
|
40
|
-
{ orientation: 'horizontal',
|
|
42
|
+
{ orientation: 'horizontal', fill: 'viewport' },
|
|
41
43
|
(r) => {
|
|
42
|
-
r.panel({ defaultSize: 40, minSize: 28 }, () => {
|
|
44
|
+
r.panel({ defaultSize: 40, minSize: 28, className: 'min-h-0 overflow-hidden' }, () => {
|
|
43
45
|
ui.auto(() => {
|
|
44
46
|
for (const row of live.rows) {
|
|
45
47
|
ui.button(row.id, {
|
|
@@ -52,11 +54,12 @@ ui.auto(() => {
|
|
|
52
54
|
}
|
|
53
55
|
});
|
|
54
56
|
});
|
|
55
|
-
r.
|
|
57
|
+
r.handle();
|
|
58
|
+
r.panel({ defaultSize: 60, minSize: 36, className: 'min-h-0 overflow-hidden' }, () => {
|
|
56
59
|
ui.auto(() => {
|
|
57
60
|
const row = live.rows.find((x) => x.id === live.selectedId);
|
|
58
61
|
if (!row) {
|
|
59
|
-
ui.
|
|
62
|
+
ui.empty({ title: 'Select a row', density: 'inline' });
|
|
60
63
|
return;
|
|
61
64
|
}
|
|
62
65
|
// detail…
|
|
@@ -68,7 +71,7 @@ ui.auto(() => {
|
|
|
68
71
|
});
|
|
69
72
|
```
|
|
70
73
|
|
|
71
|
-
Keep **separate** `ui.auto` regions for list vs detail so selection updates do not remount the whole shell. Default panel sizes (`defaultSize` / `minSize`) are percentages.
|
|
74
|
+
Keep **separate** `ui.auto` regions for list vs detail so selection updates do not remount the whole shell. Default panel sizes (`defaultSize` / `minSize`) are percentages. Prefer `fill: 'parent' | 'viewport'` over viewport `calc` height utilities.
|
|
72
75
|
|
|
73
76
|
## Live feed rows (not `ui.list`)
|
|
74
77
|
|
|
@@ -154,6 +157,49 @@ ui.auto(() => {
|
|
|
154
157
|
|
|
155
158
|
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
159
|
|
|
160
|
+
## Infinite scroll / load-more
|
|
161
|
+
|
|
162
|
+
**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`:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
ui.auto(() => {
|
|
166
|
+
if (live.loadingMore) {
|
|
167
|
+
ui.label('Loading…', { tone: 'muted', size: 'xs' });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (!live.hasMore) return;
|
|
171
|
+
ui.viewportEnter(
|
|
172
|
+
{
|
|
173
|
+
once: true,
|
|
174
|
+
root: 'nearest-scroll',
|
|
175
|
+
rootMargin: '120px',
|
|
176
|
+
onEnter: () => void loadNextPage(),
|
|
177
|
+
},
|
|
178
|
+
() => {
|
|
179
|
+
ui.label(' ').classes('h-1');
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
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`.
|
|
186
|
+
|
|
187
|
+
## Fewer classes (ops density)
|
|
188
|
+
|
|
189
|
+
Prefer semantic props over Tailwind soup:
|
|
190
|
+
|
|
191
|
+
| Instead of… | Prefer… |
|
|
192
|
+
|-------------|---------|
|
|
193
|
+
| `.classes('text-sm text-muted-foreground')` | `ui.label(…, { tone: 'muted', size: 'sm' })` |
|
|
194
|
+
| Title + subtitle stacks | `ui.pageHeading('Orders', { description: '…' })` |
|
|
195
|
+
| `className: 'flex-row w-[10rem]'` on filters | `ui.select({ inline: true, width: 'sm', … })` |
|
|
196
|
+
| `ui.spinner({ className: 'h-3 w-3' })` | `ui.spinner({ size: 'xs' })` |
|
|
197
|
+
| Feed empty muted label | `ui.empty({ title: '…', density: 'inline' })` |
|
|
198
|
+
| `h-[calc(100%-7rem)]!` on resizable | `ui.resizable({ fill: 'parent' \| 'viewport' }, …)` |
|
|
199
|
+
| `className: 'items-end justify-between'` on rows | `ui.row({ align: 'end', justify: 'between' }, …)` |
|
|
200
|
+
|
|
201
|
+
`className` remains an escape hatch.
|
|
202
|
+
|
|
157
203
|
## Logs / traces — sensitive bodies
|
|
158
204
|
|
|
159
205
|
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.12",
|
|
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.12",
|
|
41
|
+
"@close-by/clay-core": "0.2.12",
|
|
42
|
+
"@close-by/clay-components": "0.2.12",
|
|
43
|
+
"@close-by/clay-server": "0.2.12",
|
|
44
44
|
"@tailwindcss/cli": "^4.3.3",
|
|
45
45
|
"tailwindcss": "^4.3.3"
|
|
46
46
|
}
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
pagination as paginationFactory,
|
|
35
35
|
filterBar as filterBarFactory,
|
|
36
36
|
filterChips as filterChipsFactory,
|
|
37
|
+
pageHeading as pageHeadingFactory,
|
|
37
38
|
numberField as numberFieldFactory,
|
|
38
39
|
phoneInput as phoneInputFactory,
|
|
39
40
|
field as fieldFactory,
|
|
@@ -120,7 +121,10 @@ import {
|
|
|
120
121
|
type ButtonVariant,
|
|
121
122
|
type ButtonSize,
|
|
122
123
|
type LabelProps,
|
|
124
|
+
type LabelTone,
|
|
125
|
+
type LabelSize,
|
|
123
126
|
type InputProps,
|
|
127
|
+
type FieldWidth,
|
|
124
128
|
type CheckboxProps,
|
|
125
129
|
type SwitchProps,
|
|
126
130
|
type SelectProps,
|
|
@@ -144,6 +148,7 @@ import {
|
|
|
144
148
|
type BadgeProps,
|
|
145
149
|
type AlertProps,
|
|
146
150
|
type SpinnerProps,
|
|
151
|
+
type SpinnerSize,
|
|
147
152
|
type ProgressProps,
|
|
148
153
|
type SeparatorProps,
|
|
149
154
|
type IconProps,
|
|
@@ -157,6 +162,8 @@ import {
|
|
|
157
162
|
type ButtonGroupProps,
|
|
158
163
|
type ButtonGroupOrientation,
|
|
159
164
|
type EmptyProps,
|
|
165
|
+
type EmptyDensity,
|
|
166
|
+
type PageHeadingProps,
|
|
160
167
|
type PaginationProps,
|
|
161
168
|
type FilterBarProps,
|
|
162
169
|
type FilterChip,
|
|
@@ -253,11 +260,13 @@ import {
|
|
|
253
260
|
type CommandMode,
|
|
254
261
|
type CommandItemOptions,
|
|
255
262
|
type ResizableProps,
|
|
263
|
+
type ResizableFill,
|
|
256
264
|
type ResizableOrientation,
|
|
257
265
|
type ResizablePanelProps,
|
|
258
266
|
type ResizableHandleProps,
|
|
259
267
|
type ScrollAreaProps,
|
|
260
268
|
type ViewportEnterProps,
|
|
269
|
+
type ViewportEnterRoot,
|
|
261
270
|
type KeybindProps,
|
|
262
271
|
type KbdProps,
|
|
263
272
|
type BreadcrumbItem,
|
|
@@ -284,7 +293,6 @@ import {
|
|
|
284
293
|
type HtmlProps,
|
|
285
294
|
type ImageProps,
|
|
286
295
|
type IframeProps,
|
|
287
|
-
type ViewportEnterProps,
|
|
288
296
|
type UploadProps,
|
|
289
297
|
type UploadVariant,
|
|
290
298
|
type UploadedFile,
|
|
@@ -494,7 +502,10 @@ export type {
|
|
|
494
502
|
ButtonVariant,
|
|
495
503
|
ButtonSize,
|
|
496
504
|
LabelProps,
|
|
505
|
+
LabelTone,
|
|
506
|
+
LabelSize,
|
|
497
507
|
InputProps,
|
|
508
|
+
FieldWidth,
|
|
498
509
|
CheckboxProps,
|
|
499
510
|
BadgeProps,
|
|
500
511
|
LinkProps,
|
|
@@ -555,6 +566,7 @@ export type {
|
|
|
555
566
|
AvatarProps,
|
|
556
567
|
SkeletonProps,
|
|
557
568
|
SpinnerProps,
|
|
569
|
+
SpinnerSize,
|
|
558
570
|
ProgressProps,
|
|
559
571
|
SeparatorProps,
|
|
560
572
|
IconProps,
|
|
@@ -571,6 +583,7 @@ export type {
|
|
|
571
583
|
ImageProps,
|
|
572
584
|
IframeProps,
|
|
573
585
|
ViewportEnterProps,
|
|
586
|
+
ViewportEnterRoot,
|
|
574
587
|
UploadProps,
|
|
575
588
|
UploadVariant,
|
|
576
589
|
UploadedFile,
|
|
@@ -930,6 +943,11 @@ export function empty(props: EmptyProps, footer?: () => void): Element {
|
|
|
930
943
|
return emptyFactory(props, footer);
|
|
931
944
|
}
|
|
932
945
|
|
|
946
|
+
/** Page title + optional description. Prefer over stacked muted labels. */
|
|
947
|
+
export function pageHeading(props: PageHeadingProps, trailing?: () => void): Element {
|
|
948
|
+
return pageHeadingFactory(props, trailing);
|
|
949
|
+
}
|
|
950
|
+
|
|
933
951
|
/** Standalone pager. See {@link PaginationProps}. */
|
|
934
952
|
export function pagination(props?: PaginationProps): Element {
|
|
935
953
|
return paginationFactory(props);
|
|
@@ -1788,6 +1806,7 @@ export const ui = {
|
|
|
1788
1806
|
feedList,
|
|
1789
1807
|
buttonGroup,
|
|
1790
1808
|
empty,
|
|
1809
|
+
pageHeading,
|
|
1791
1810
|
pagination,
|
|
1792
1811
|
filterBar,
|
|
1793
1812
|
filterChips,
|
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();
|