@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 +160 -0
- package/dist/rip-ui.min.js +122 -122
- package/dist/rip-ui.min.js.br +0 -0
- package/package.json +2 -2
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
|