@rip-lang/ui 0.3.13 → 0.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,6 +45,73 @@ export Home = component
45
45
 
46
46
  Run `bun index.rip`, open `http://localhost:3000`.
47
47
 
48
+ ## The Two Keywords
49
+
50
+ Rip UI adds two keywords to the language: `component` and `render`. Each
51
+ serves a distinct role, and together they form a complete reactive UI model.
52
+
53
+ ### `component` — the model
54
+
55
+ Raw Rip Lang has no concept of a self-contained, reusable UI unit. The
56
+ `component` keyword adds everything needed to manage interactive state:
57
+
58
+ - **Reactive state** (`:=`) — assignments create signals that trigger
59
+ updates automatically. `count := 0` is not a plain variable; changing
60
+ it updates the DOM.
61
+ - **Computed values** (`~=`) — derived values that recalculate when their
62
+ dependencies change. `remaining ~= todos.filter((t) -> not t.done).length`
63
+ - **Effects** (`~>`) — side effects that run whenever reactive dependencies
64
+ change. `~> @app.data.count = count`
65
+ - **Props** (`@` prefix, `=!` for readonly) — a public API for parent
66
+ components to pass data in, with signal passthrough for shared reactivity.
67
+ - **Lifecycle hooks** — `beforeMount`, `mounted`, `updated`, `beforeUnmount`,
68
+ `unmounted` for running code at specific points in a component's life.
69
+ - **Context API** — `setContext` and `getContext` for ancestor-to-descendant
70
+ data sharing without prop drilling.
71
+ - **Mount/unmount mechanics** — attaching to the DOM, cascading teardown
72
+ to children, and keep-alive caching across navigation.
73
+ - **Encapsulation** — each component is a class with its own scope, state,
74
+ and methods. No global variable collisions, no leaking internals.
75
+
76
+ A component without a render block can still hold state, run effects, and
77
+ participate in the component tree — it just has no visual output.
78
+
79
+ ### `render` — the view
80
+
81
+ The `render` keyword provides a declarative template DSL for describing DOM
82
+ structure. It has its own lexer pass and syntax rules distinct from regular
83
+ Rip code:
84
+
85
+ - **Element creation** — tag names become DOM nodes: `div`, `h1`, `button`
86
+ - **CSS-selector shortcuts** — `div.card.active`, `#main`, `.card` (implicit `div`)
87
+ - **Dynamic classes** — `div.('card', active && 'active')` with CLSX semantics
88
+ - **Event handlers** — `@click: handler` compiles to `addEventListener`
89
+ - **Two-way binding** — `value <=> username` wires reactive read and write
90
+ (see [Two-Way Binding](#two-way-binding--the--operator) below)
91
+ - **Conditionals and loops** — `if`/`else` and `for item in items` with
92
+ anchor-based DOM insertion and keyed reconciliation
93
+ - **Children/slots** — `@children` receives child nodes, `#content` marks
94
+ layout insertion points
95
+ - **Component instantiation** — PascalCase names like `Card title: "Hello"`
96
+ resolve to components automatically, no imports needed
97
+
98
+ Render compiles to two methods: `_create()` builds the DOM tree once, and
99
+ `_setup()` wires reactive effects for fine-grained updates. There is no
100
+ virtual DOM — each reactive binding creates a direct DOM effect that updates
101
+ the specific text node or attribute that depends on it.
102
+
103
+ A render block can only exist inside a component. It needs the component's
104
+ signals, computed values, and lifecycle to have something to render and
105
+ react to.
106
+
107
+ ### Together
108
+
109
+ `component` provides the **model** — state, reactivity, lifecycle, identity.
110
+ `render` provides the **view** — a concise way to describe what the DOM
111
+ should look like and how it stays in sync with that state. One defines
112
+ behavior, the other defines structure. Neither is useful without the other
113
+ in practice, but they are separate concerns with separate syntax.
114
+
48
115
  ## Component Composition
49
116
 
50
117
  Page components in `pages/` map to routes via file-based routing. Shared
@@ -203,6 +270,99 @@ button.('flex items-center rounded-lg')
203
270
  Blank lines between attributes and children are fine — they don't break the
204
271
  structure.
205
272
 
273
+ ## Two-Way Binding — The `<=>` Operator
274
+
275
+ The `<=>` operator is one of Rip UI's most powerful features. It creates a
276
+ bidirectional reactive binding between a parent's state and a child element
277
+ or component — changes flow in both directions automatically.
278
+
279
+ ### The Problem It Solves
280
+
281
+ In React, wiring state to interactive elements requires explicit value props
282
+ and callback handlers for every bindable property:
283
+
284
+ ```jsx
285
+ // React: verbose, repetitive ceremony
286
+ const [name, setName] = useState('');
287
+ const [role, setRole] = useState('viewer');
288
+ const [notify, setNotify] = useState(true);
289
+ const [showConfirm, setShowConfirm] = useState(false);
290
+
291
+ <input value={name} onChange={e => setName(e.target.value)} />
292
+ <Select value={role} onValueChange={setRole} />
293
+ <Switch checked={notify} onCheckedChange={setNotify} />
294
+ <Dialog open={showConfirm} onOpenChange={setShowConfirm} />
295
+ ```
296
+
297
+ Every bindable property needs a state declaration AND a setter callback.
298
+ This is the single most tedious pattern in React development.
299
+
300
+ ### The Rip Way
301
+
302
+ In Rip, `<=>` replaces all of that with a single operator:
303
+
304
+ ```coffee
305
+ export UserForm = component
306
+ @name := ''
307
+ @role := 'viewer'
308
+ @notify := true
309
+ @showConfirm := false
310
+
311
+ render
312
+ input value <=> @name
313
+ Select value <=> @role
314
+ Option value: "viewer", "Viewer"
315
+ Option value: "editor", "Editor"
316
+ Option value: "admin", "Admin"
317
+ Switch checked <=> @notify
318
+ Dialog open <=> @showConfirm
319
+ p "Save changes?"
320
+ ```
321
+
322
+ No `onChange`. No `onValueChange`. No `onOpenChange`. No `setName`, `setRole`,
323
+ `setNotify`, `setShowConfirm`. The reactive system handles everything — state
324
+ flows down, user interactions flow back up.
325
+
326
+ ### How It Works
327
+
328
+ `value <=> username` compiles to two things:
329
+
330
+ 1. **State → DOM** (reactive effect): `__effect(() => { el.value = username; })`
331
+ 2. **DOM → State** (event listener): `el.addEventListener('input', (e) => { username = e.target.value; })`
332
+
333
+ The compiler is smart about types:
334
+ - `value <=>` on text inputs uses the `input` event and `e.target.value`
335
+ - `value <=>` on number/range inputs uses `e.target.valueAsNumber`
336
+ - `checked <=>` uses the `change` event and `e.target.checked`
337
+
338
+ For custom components, `<=>` passes the reactive signal itself, enabling the
339
+ child to both read and write the parent's state directly — no callback
340
+ indirection.
341
+
342
+ ### Auto-Detection
343
+
344
+ Even without `<=>`, the compiler auto-detects when `value:` or `checked:` is
345
+ bound to a reactive expression and generates two-way binding automatically:
346
+
347
+ ```coffee
348
+ # These are equivalent:
349
+ input value <=> @name # explicit two-way binding
350
+ input value: @name # auto-detected (name is reactive)
351
+ ```
352
+
353
+ ### Why This Matters
354
+
355
+ Two-way binding is what Vue has with `v-model`, what Svelte has with `bind:`,
356
+ and what Angular has with `[(ngModel)]`. React is the only major framework
357
+ that deliberately omits it, forcing the verbose controlled component pattern
358
+ instead.
359
+
360
+ Rip's `<=>` goes further than Vue or Svelte — it works uniformly across HTML
361
+ elements and custom components with the same syntax. A `Dialog open <=> show`
362
+ and an `input value <=> name` use the same operator, the same mental model,
363
+ and the same compilation strategy. This makes headless interactive components
364
+ dramatically cleaner to use than their React equivalents.
365
+
206
366
  ## How It Works
207
367
 
208
368
  The browser loads one file — `rip-ui.min.js` (~52KB Brotli) — which bundles the