@weftui/core 0.26.1 → 0.26.3
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/README.md +60 -0
- package/docs/explanation/boundaries-and-suspense.md +91 -0
- package/docs/explanation/combinator-api.md +164 -0
- package/docs/explanation/reactive-primitives.md +162 -0
- package/docs/explanation/rendering-model.md +65 -0
- package/docs/explanation/services-and-context.md +91 -0
- package/docs/how-to/add-routing.md +306 -0
- package/docs/how-to/author-components.md +264 -0
- package/docs/how-to/handle-forms.md +76 -0
- package/docs/how-to/load-async-data.md +70 -0
- package/docs/how-to/load-data-with-rpc.md +172 -0
- package/docs/how-to/provide-services.md +124 -0
- package/docs/how-to/render-keyed-lists.md +51 -0
- package/docs/how-to/render-on-the-server.md +86 -0
- package/docs/how-to/show-navigation-progress.md +65 -0
- package/docs/how-to/split-routes-lazily.md +62 -0
- package/docs/how-to/style-reactively.md +63 -0
- package/docs/how-to/use-element-refs.md +63 -0
- package/docs/index.md +59 -0
- package/docs/reference/core.md +496 -0
- package/docs/reference/dom.md +142 -0
- package/docs/reference/router.md +352 -0
- package/docs/tutorial/01-your-first-app.md +48 -0
- package/docs/tutorial/02-reactivity.md +57 -0
- package/docs/tutorial/03-services-and-async.md +77 -0
- package/docs/tutorial/04-errors-and-server.md +61 -0
- package/package.json +19 -4
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Style Reactively
|
|
3
|
+
order: 10
|
|
4
|
+
section: how-to
|
|
5
|
+
description: Drive inline styles from streams — a single property, or a whole style object — so the DOM updates in place with CSS transitions.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Style Reactively
|
|
9
|
+
|
|
10
|
+
**Goal:** animate or react to state in an element's inline style without re-rendering — a single CSS property, or a whole style object, driven by a stream.
|
|
11
|
+
|
|
12
|
+
The `style` prop accepts the [`Source`](https://weftui.dev/docs/explanation/reactive-primitives) vocabulary at any level: a property value can be a stream, and you can spread a stream of style objects. CSS `transition` composes naturally, because the renderer mutates the existing node in place.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { h } from "@weftui/core";
|
|
16
|
+
import { Schedule, Stream } from "effect";
|
|
17
|
+
|
|
18
|
+
const AnimatedHue = () => {
|
|
19
|
+
const hue = Stream.iterate(0, (h) => (h + 2) % 360).pipe(
|
|
20
|
+
Stream.schedule(Schedule.spaced("50 millis")),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
return h.div(
|
|
24
|
+
{
|
|
25
|
+
class: "demo-box",
|
|
26
|
+
style: {
|
|
27
|
+
// one property is reactive; the rest are static
|
|
28
|
+
backgroundColor: Stream.map(hue, (h) => `hsl(${h}, 70%, 60%)`),
|
|
29
|
+
transition: "background-color 0.05s",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
"Hue",
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Three modes
|
|
38
|
+
|
|
39
|
+
1. **A single property as a stream** — as above: one key's value is a `Stream`, the others are static strings. Each stream property is subscribed independently.
|
|
40
|
+
2. **A static object** — an ordinary `style: { backgroundColor: "#667eea" }` with no streams; nothing updates.
|
|
41
|
+
3. **A whole style object as a stream** — spread a stream that emits complete style objects, merged with static props:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const pulse = Stream.make(1, 0.5).pipe(
|
|
45
|
+
Stream.schedule(Schedule.spaced("800 millis")),
|
|
46
|
+
Stream.forever,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
h.div({ style: { ...pulse, transition: "opacity 0.4s ease-in-out" } }, "Pulse");
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Each emitted object is merged with the static properties on the element.
|
|
53
|
+
|
|
54
|
+
## Notes
|
|
55
|
+
|
|
56
|
+
- **Property names are camelCase** (`backgroundColor`, `boxShadow`) — the same keys as the DOM `style` object.
|
|
57
|
+
- **CSS transitions just work.** Because a stream emission patches the DOM node directly (no re-render), the browser applies the `transition` as it would for any style mutation.
|
|
58
|
+
- **Pace with `Schedule`.** `Stream.iterate`/`Stream.make` paced by `Stream.schedule(Schedule.spaced(…))` and looped with `Stream.forever` is the idiom for time-based style animation; combine with any Effect timing you like.
|
|
59
|
+
|
|
60
|
+
## See also
|
|
61
|
+
|
|
62
|
+
- [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives) — reactive style props and the `Source` vocabulary
|
|
63
|
+
- [examples/reactive-styles](https://github.com/stefvw93/weft/tree/main/examples/reactive-styles) — per-property and whole-object stream styles with CSS transitions
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Element Refs
|
|
3
|
+
order: 11
|
|
4
|
+
section: how-to
|
|
5
|
+
description: Capture a DOM element with the ref prop into a SubscriptionRef<Option<HTMLElement>>, then react to its mount with a scoped observer or read it imperatively.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Use Element Refs
|
|
9
|
+
|
|
10
|
+
**Goal:** get a handle to a real DOM element — to focus it, measure it, or call an imperative browser API on it.
|
|
11
|
+
|
|
12
|
+
Declare a `SubscriptionRef<Option<HTMLElement>>`, attach it with the `ref` prop, and either **react** to the element appearing (a scoped observer on `.changes`) or **read** it later inside a handler.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { h } from "@weftui/core";
|
|
16
|
+
import { Effect, Option, pipe, Stream, SubscriptionRef } from "effect";
|
|
17
|
+
|
|
18
|
+
const AutoFocusInput = () =>
|
|
19
|
+
Effect.gen(function* () {
|
|
20
|
+
const inputRef = yield* SubscriptionRef.make<Option.Option<HTMLInputElement>>(Option.none());
|
|
21
|
+
|
|
22
|
+
// Observe the element becoming available, once, and focus it.
|
|
23
|
+
yield* pipe(
|
|
24
|
+
inputRef.changes,
|
|
25
|
+
Stream.filter(Option.isSome),
|
|
26
|
+
Stream.take(1),
|
|
27
|
+
Stream.runForEach((el) => Effect.sync(() => el.value.focus())),
|
|
28
|
+
Effect.forkScoped, // ← ties the observer to the component's instance scope
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return yield* h.input({ ref: inputRef, type: "text", placeholder: "I'm focused!" });
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
|
|
37
|
+
- **The `ref` prop** takes a `SubscriptionRef<Option<T>>`. The renderer sets it to `Option.some(element)` **once**, when the element is created — so the ref is an `Option`: `None` until mount, `Some(el)` after.
|
|
38
|
+
- **React to mount** by observing `ref.changes`: `Stream.filter(Option.isSome)` waits for the element, `Stream.take(1)` takes just the first appearance, and `Stream.runForEach` does the imperative work. This is the equivalent of a mount effect.
|
|
39
|
+
- **Use `Effect.forkScoped`, not `Effect.fork`.** `forkScoped` ties the observer fiber to the component's **instance scope** (the ambient `Scope` the renderer provides), so it lives as long as the component is mounted. A bare `Effect.fork` binds to the transient component-body fiber and is interrupted the instant the generator returns — the observer would never fire.
|
|
40
|
+
|
|
41
|
+
## Read a ref imperatively
|
|
42
|
+
|
|
43
|
+
When you only need the element later (e.g. in a click handler), skip the observer and read the ref on demand:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const scroll = () =>
|
|
47
|
+
Effect.gen(function* () {
|
|
48
|
+
const el = yield* SubscriptionRef.get(targetRef);
|
|
49
|
+
if (Option.isSome(el)) el.value.scrollIntoView({ behavior: "smooth" });
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Notes
|
|
54
|
+
|
|
55
|
+
- A plain `Ref` suffices if you **only** read the element imperatively; use `SubscriptionRef` when you need to **react** to it becoming available.
|
|
56
|
+
- Refs are set once at element creation and are not cleared on unmount.
|
|
57
|
+
- Coming from React: `SubscriptionRef.make<Option<T>>(Option.none())` ↔ `useRef<T>(null)`; the `Stream.filter(Option.isSome)` observer ↔ a `useEffect` mount guard.
|
|
58
|
+
|
|
59
|
+
## See also
|
|
60
|
+
|
|
61
|
+
- [Reactive Primitives](https://weftui.dev/docs/explanation/reactive-primitives) — `SubscriptionRef` and `.changes`
|
|
62
|
+
- [Author Components](https://weftui.dev/docs/how-to/author-components) — instance scope and `Effect.forkScoped`
|
|
63
|
+
- [examples/element-ref](https://github.com/stefvw93/weft/tree/main/examples/element-ref) — auto-focus, element measurement, and imperative scroll via refs
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Weft Documentation
|
|
2
|
+
|
|
3
|
+
**Reactive UI, woven from Effect.**
|
|
4
|
+
|
|
5
|
+
Weft is an Effect-native reactive DOM library — in the browser and on the server. `Node<E, R>` is `Effect.Effect<ElementDescriptor, E, R>`: every element is an Effect, so error and requirement channels accumulate through the tree, all Effect combinators apply to nodes directly, and services flow from mount through the whole app. Streams drive every update — there is no virtual DOM — and the same tree renders to HTML on the server and `hydrate()`s in place on the client, flash-free. No JSX.
|
|
6
|
+
|
|
7
|
+
The docs follow the [Diátaxis](https://diataxis.fr) model. Pick your entry point by what you are trying to do:
|
|
8
|
+
|
|
9
|
+
## Start here
|
|
10
|
+
|
|
11
|
+
**[→ Tutorial](https://weftui.dev/docs/tutorial/01-your-first-app)** — a four-step guided path from a static component to a server-rendered, error-handled app. Start here if you are new to Weft:
|
|
12
|
+
|
|
13
|
+
1. [Your First App](https://weftui.dev/docs/tutorial/01-your-first-app) — `h` and `mount`
|
|
14
|
+
2. [Reactivity](https://weftui.dev/docs/tutorial/02-reactivity) — `SubscriptionRef` and streams
|
|
15
|
+
3. [Services and Async](https://weftui.dev/docs/tutorial/03-services-and-async) — handlers, services, async loading
|
|
16
|
+
4. [Errors and Server Rendering](https://weftui.dev/docs/tutorial/04-errors-and-server) — boundaries and SSR
|
|
17
|
+
|
|
18
|
+
## The four quadrants
|
|
19
|
+
|
|
20
|
+
| | |
|
|
21
|
+
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| **[Tutorial](https://weftui.dev/docs/tutorial/01-your-first-app)** | Learning-oriented. One guided path, start to finish. |
|
|
23
|
+
| **[How-to guides](https://weftui.dev/docs/how-to/author-components)** | Task-oriented. Author components, render on the server, load data with rpc, add routing — plus recipes for forms, async data, keyed lists, reactive styles, refs, and lazy routing. |
|
|
24
|
+
| **[Explanation](https://weftui.dev/docs/explanation/rendering-model)** | Understanding-oriented. The rendering model, the combinator API, reactive primitives, boundaries, and services & context. |
|
|
25
|
+
| **[Reference](https://weftui.dev/docs/reference/core)** | Information-oriented. Full API: [`@weftui/core`](https://weftui.dev/docs/reference/core), [`@weftui/dom`](https://weftui.dev/docs/reference/dom), [`@weftui/router`](https://weftui.dev/docs/reference/router). |
|
|
26
|
+
|
|
27
|
+
New to the model itself? Read [The Rendering Model](https://weftui.dev/docs/explanation/rendering-model) — why there is no virtual DOM, and what "streams are the weft" means.
|
|
28
|
+
|
|
29
|
+
## Packages
|
|
30
|
+
|
|
31
|
+
Three published packages make up Weft's public API, plus one build-time plugin:
|
|
32
|
+
|
|
33
|
+
- **`@weftui/core`** — element builders (`h`), components, sources/streams, and boundaries. Start here.
|
|
34
|
+
- **`@weftui/dom`** — the renderer: `./client` (`mount`/`hydrate`) and `./server` (`renderToString*`).
|
|
35
|
+
- **`@weftui/router`** — universal nested routing, `Router.lazy`, and the rpc seam.
|
|
36
|
+
- **`@weftui/vite`** — a build-time Vite plugin (tooling, not a runtime API).
|
|
37
|
+
|
|
38
|
+
`@weftui/base` is an internal, currently-empty stub — it has no public primitives; ignore it.
|
|
39
|
+
|
|
40
|
+
## Examples
|
|
41
|
+
|
|
42
|
+
The [`examples/`](https://github.com/stefvw93/weft/tree/main/examples) directory contains standalone runnable apps. Each covers a specific pattern and ships with a browser test:
|
|
43
|
+
|
|
44
|
+
| Example | What it shows |
|
|
45
|
+
| ---------------------------- | ------------------------------------------------------------------------------------ |
|
|
46
|
+
| `async-data-loading` | Loading states, retry, error boundaries with Stream and Effect |
|
|
47
|
+
| `declarative-event-handlers` | Plain, Effect-returning, service-aware, and reactive handlers |
|
|
48
|
+
| `element-ref` | DOM refs with `SubscriptionRef<Option<HTMLElement>>` |
|
|
49
|
+
| `error-boundary` | All six failure-catch `Boundary.*` variants |
|
|
50
|
+
| `form-handling` | Reactive inputs, Schema validation, Effect submit handlers |
|
|
51
|
+
| `keyed-list` | Keyed list rendering with `List.each` |
|
|
52
|
+
| `list-rendering` | Static and stream-based lists, fragments, nested iterables |
|
|
53
|
+
| `reactive-styles` | Per-property and whole-object stream styles, CSS transitions |
|
|
54
|
+
| `router-ssr` | Universal nested routing with SSR, hydration, layouts, `Boundary.rpc`, `Router.lazy` |
|
|
55
|
+
| `server-boundary` | `Boundary.rpc` client-first mount + refetch, router-less |
|
|
56
|
+
| `ssr-hydration` | SSR + hydration without server data loading |
|
|
57
|
+
| `subscription-ref` | Local state, derived streams, coordinating multiple refs |
|
|
58
|
+
| `suspense` | Suspense boundaries for streaming SSR and client coordination |
|
|
59
|
+
| `type-augmentation` | Typed custom elements on `h` via the `CustomElements` interface |
|