@rip-lang/ui 0.3.14 → 0.3.16

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
@@ -87,6 +87,7 @@ Rip code:
87
87
  - **Dynamic classes** — `div.('card', active && 'active')` with CLSX semantics
88
88
  - **Event handlers** — `@click: handler` compiles to `addEventListener`
89
89
  - **Two-way binding** — `value <=> username` wires reactive read and write
90
+ (see [Two-Way Binding](#two-way-binding--the--operator) below)
90
91
  - **Conditionals and loops** — `if`/`else` and `for item in items` with
91
92
  anchor-based DOM insertion and keyed reconciliation
92
93
  - **Children/slots** — `@children` receives child nodes, `#content` marks
@@ -269,6 +270,99 @@ button.('flex items-center rounded-lg')
269
270
  Blank lines between attributes and children are fine — they don't break the
270
271
  structure.
271
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
+
272
366
  ## How It Works
273
367
 
274
368
  The browser loads one file — `rip-ui.min.js` (~52KB Brotli) — which bundles the