@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 +94 -0
- package/dist/rip-ui.min.js +174 -164
- package/dist/rip-ui.min.js.br +0 -0
- package/package.json +1 -1
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
|