rip-lang 3.7.4 → 3.8.9

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/docs/demo.html ADDED
@@ -0,0 +1,342 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Rip UI Demo</title>
7
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg width='420' height='420' viewBox='0 0 420 420' fill='none' xmlns='http://www.w3.org/2000/svg'><circle cx='210' cy='210' r='210' fill='white'/><path d='M114.5 263C79.772 263 45.7872 275.051 34.271 283.579C33.4994 284.151 34.012 285.229 34.9656 285.117C73.1916 280.629 115.309 292.74 146.5 304C178.5 315.552 221 336 269 336C314.651 336 372.074 310.499 386.401 293.944C387.038 293.208 386.301 292.353 385.435 292.799C376.614 297.341 364.243 306.018 316.073 306.948C265.273 307.928 159 263 114.5 263Z' fill='%230389FF'/><path d='M223.46 84C239.53 84 253.592 86.9253 265.645 92.7754C277.697 98.6254 287.071 107.048 293.767 118.043C300.462 129.038 303.811 142.219 303.811 157.584C303.811 173.09 300.357 186.165 293.449 196.808C287.068 206.742 278.259 214.402 267.026 219.792L309.603 297.958C297.885 297.851 283.094 295.413 266.52 291.62C257.58 289.574 248.214 287.157 238.645 284.547L209.127 229.054H188.782V270.333C176.996 266.968 165.515 263.788 154.823 261.15C146.038 258.984 137.665 257.152 130 255.885V84H223.46ZM188.782 183.381H209.505C216.412 183.381 222.297 182.535 227.16 180.844C232.094 179.082 235.865 176.297 238.473 172.491C241.151 168.685 242.49 163.716 242.49 157.584C242.49 151.382 241.151 146.342 238.473 142.466C235.865 138.519 232.094 135.628 227.16 133.796C222.297 131.893 216.412 130.941 209.505 130.941H188.782V183.381Z' fill='%23BB0000'/></svg>">
8
+ <style>
9
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
10
+
11
+ /**
12
+ * Rip UI Demo — Self-contained styles
13
+ */
14
+
15
+ /* Design Tokens */
16
+ :root {
17
+ --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
18
+ --gray-50: #f9fafb; --gray-100: #f3f4f6; --gray-200: #e5e7eb;
19
+ --gray-300: #d1d5db; --gray-400: #9ca3af; --gray-500: #6b7280;
20
+ --gray-600: #4b5563; --gray-700: #374151; --gray-800: #1f2937; --gray-900: #111827;
21
+ --blue-50: #eff6ff; --blue-100: #dbeafe; --blue-200: #bfdbfe; --blue-500: #3b82f6;
22
+ --blue-600: #2563eb; --blue-700: #1d4ed8;
23
+ --green-50: #f0fdf4; --green-500: #22c55e; --green-600: #16a34a;
24
+ --red-50: #fef2f2; --red-500: #ef4444;
25
+ --surface: var(--gray-50); --card: #ffffff;
26
+ --text: var(--gray-900); --text-secondary: var(--gray-500); --text-muted: var(--gray-400);
27
+ --border: var(--gray-200); --accent: var(--blue-600); --accent-hover: var(--blue-700);
28
+ --radius: 0.75rem; --radius-lg: 1rem;
29
+ --shadow: 0 1px 3px rgb(0 0 0 / 0.1), 0 1px 2px rgb(0 0 0 / 0.06);
30
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
31
+ --transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
32
+ }
33
+
34
+ /* Reset */
35
+ *, *::before, *::after { box-sizing: border-box; }
36
+ * { margin: 0; }
37
+ html { -webkit-text-size-adjust: 100%; }
38
+ body { font-family: var(--font); line-height: 1.6; color: var(--text); background: var(--surface); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
39
+ a { color: inherit; text-decoration: none; }
40
+ img, svg { display: block; max-width: 100%; }
41
+ input, button, textarea, select { font: inherit; }
42
+ #app { max-width: 52rem; margin: 0 auto; padding: 2rem; }
43
+
44
+ /* Layout Shell */
45
+ .app-layout { min-height: 100vh; display: flex; flex-direction: column; }
46
+ .app-nav { background: var(--card); border-bottom: 1px solid var(--border); border-radius: var(--radius); padding: 0 1.5rem; position: sticky; top: 0; z-index: 100; }
47
+ .nav-inner { max-width: 48rem; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; height: 3.5rem; }
48
+ .nav-brand { display: flex; align-items: center; gap: 0.5rem; font-weight: 600; font-size: 0.9375rem; letter-spacing: -0.02em; }
49
+ .nav-links { display: flex; align-items: center; gap: 0.25rem; }
50
+ .nav-link { padding: 0.375rem 0.75rem; border-radius: 0.5rem; color: var(--text-secondary); font-size: 0.8125rem; font-weight: 500; transition: color var(--transition), background-color var(--transition); }
51
+ .nav-link:hover { color: var(--text); background-color: var(--gray-100); }
52
+ .nav-link:active { background-color: var(--gray-200); }
53
+ .nav-link.active { color: var(--accent); background-color: var(--blue-50); }
54
+ .nav-link.active:hover { color: var(--accent-hover); background-color: var(--blue-100); }
55
+ .nav-link.active:active { background-color: var(--blue-200); }
56
+ .app-main { flex: 1; max-width: 48rem; width: 100%; margin: 0 auto; padding: 2rem 1.5rem; }
57
+
58
+ /* Page Sections */
59
+ .page-header { margin-bottom: 2rem; }
60
+ .page-title { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.03em; line-height: 1.2; color: var(--text); }
61
+ .page-subtitle { font-size: 0.875rem; color: var(--text-secondary); margin-top: 0.25rem; }
62
+
63
+ /* Card */
64
+ .card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow); padding: 1.5rem; }
65
+ .card + .card { margin-top: 1rem; }
66
+ .card h3 { font-size: 0.8125rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-secondary); margin-bottom: 1rem; }
67
+ .card-title { font-size: 0.8125rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-secondary); margin-bottom: 1rem; }
68
+
69
+ /* Button */
70
+ .btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; padding: 0.5rem 1rem; border: 1px solid var(--border); border-radius: 0.5rem; background-color: var(--card); color: var(--text); font-size: 0.8125rem; font-weight: 500; cursor: pointer; transition: color var(--transition), background-color var(--transition), border-color var(--transition), transform var(--transition); user-select: none; }
71
+ .btn:hover { background-color: var(--gray-50); border-color: var(--gray-300); }
72
+ .btn:active { transform: scale(0.98); }
73
+ .btn-primary { background: var(--accent); border-color: var(--accent); color: white; }
74
+ .btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
75
+ .btn-danger { color: var(--red-500); border-color: var(--red-500); }
76
+ .btn-danger:hover { background: var(--red-50); }
77
+ .btn-sm { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
78
+ .btn-group { display: flex; gap: 0.5rem; flex-wrap: wrap; }
79
+
80
+ /* Counter */
81
+ .counter-display { font-size: 4rem; font-weight: 700; letter-spacing: -0.04em; text-align: center; padding: 2rem 0; font-variant-numeric: tabular-nums; color: var(--text); transition: color var(--transition); }
82
+ .counter-display.positive { color: var(--green-600); }
83
+ .counter-display.negative { color: var(--red-500); }
84
+ .counter-controls { display: flex; justify-content: center; gap: 0.5rem; }
85
+
86
+ /* Input */
87
+ .input { width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--border); border-radius: 0.5rem; background-color: var(--card); color: var(--text); font-size: 0.875rem; transition: border-color var(--transition), box-shadow var(--transition); }
88
+ .input::placeholder { color: var(--text-muted); }
89
+ .input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--blue-100); }
90
+
91
+ /* Todo List */
92
+ .todo-input-row { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
93
+ .todo-input-row .input { flex: 1; }
94
+ .todo-list { list-style: none; padding: 0; }
95
+ .todo-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem 0; border-bottom: 1px solid var(--gray-100); transition: opacity var(--transition); }
96
+ .todo-item:last-child { border-bottom: none; }
97
+ .todo-text { flex: 1; font-size: 0.875rem; color: var(--text); }
98
+ .todo-text.done { text-decoration: line-through; color: var(--text-muted); }
99
+ .todo-delete { opacity: 0; padding: 0.125rem 0.375rem; border: none; background: none; color: var(--text-muted); cursor: pointer; border-radius: 0.25rem; font-size: 0.75rem; transition: opacity var(--transition), color var(--transition), background-color var(--transition); }
100
+ .todo-item:hover .todo-delete { opacity: 1; }
101
+ .todo-delete:hover { color: var(--red-500); background: var(--red-50); }
102
+ .todo-stats { display: flex; justify-content: space-between; padding-top: 0.75rem; border-top: 1px solid var(--gray-100); font-size: 0.75rem; color: var(--text-secondary); }
103
+
104
+ /* Feature Grid */
105
+ .feature-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; }
106
+ @media (max-width: 640px) { .feature-grid { grid-template-columns: 1fr; } }
107
+ .feature-card { background-color: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem; transition: border-color var(--transition), box-shadow var(--transition); }
108
+ .feature-card:hover { border-color: var(--gray-300); box-shadow: var(--shadow); }
109
+ .feature-icon { font-size: 1.5rem; margin-bottom: 0.75rem; }
110
+ .feature-name { font-size: 0.875rem; font-weight: 600; color: var(--text); margin-bottom: 0.25rem; }
111
+ .feature-desc { font-size: 0.8125rem; color: var(--text-secondary); line-height: 1.5; }
112
+
113
+ /* Stat badge */
114
+ .stat { display: inline-flex; align-items: center; gap: 0.375rem; padding: 0.375rem 0.75rem; background: var(--gray-100); border-radius: var(--radius); font-size: 0.8125rem; font-weight: 500; color: var(--text-secondary); }
115
+ .stat-value { font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
116
+
117
+ /* Error banner */
118
+ .error-banner { background: var(--red-50); border: 1px solid var(--red-500); border-radius: var(--radius); padding: 1rem; margin-bottom: 1rem; display: flex; align-items: center; justify-content: space-between; }
119
+ </style>
120
+ <script type="module" src="dist/rip-ui.min.js"></script>
121
+ </head>
122
+ <body>
123
+ <div id="app"></div>
124
+
125
+ <script type="text/rip">
126
+ { launch } = importRip!('dist/ui.rip')
127
+
128
+ launch window.location.pathname,
129
+ target: '#app'
130
+ persist: true
131
+ hash: true
132
+ bundle:
133
+ components:
134
+ 'components/_layout.rip': '''
135
+ # Root Layout (with error boundary and navigation indicator)
136
+
137
+ export Layout = component
138
+ errorMsg := null
139
+
140
+ onError: (err) ->
141
+ errorMsg = err.message
142
+
143
+ render
144
+ .app-layout
145
+ nav.app-nav
146
+ .nav-inner
147
+ a.nav-brand "Rip UI"
148
+ .nav-links
149
+ a.('nav-link', @router.path is '/' and 'active') href: "#", "Home"
150
+ a.('nav-link', @router.path is '/counter' and 'active') href: "#counter", "Counter"
151
+ a.('nav-link', @router.path is '/todos' and 'active') href: "#todos", "Todos"
152
+ a.('nav-link', @router.path is '/about' and 'active') href: "#about", "About"
153
+ if @router.navigating
154
+ span.nav-loading "Loading..."
155
+ main.app-main
156
+ if errorMsg
157
+ .error-banner
158
+ p "Something went wrong: #{errorMsg}"
159
+ button.btn @click: (-> errorMsg = null), "Dismiss"
160
+ #content
161
+ '''
162
+
163
+ 'components/index.rip': '''
164
+ # Home Page
165
+
166
+ export Home = component
167
+
168
+ render
169
+ div
170
+ .page-header
171
+ h1.page-title "Rip UI Framework!"
172
+ p.page-subtitle "Zero-build reactive web apps"
173
+
174
+ .feature-grid
175
+ .feature-card
176
+ .feature-icon "🗂"
177
+ .feature-name "Component Store"
178
+ .feature-desc "Component source files loaded on demand. Compiled in the browser."
179
+
180
+ .feature-card
181
+ .feature-icon "🔀"
182
+ .feature-name "File-Based Router"
183
+ .feature-desc "URLs map to components. Dynamic segments and nested layouts."
184
+
185
+ .feature-card
186
+ .feature-icon "⚡"
187
+ .feature-name "Reactive Stash"
188
+ .feature-desc "Deep state tree with path navigation. Every property tracked."
189
+
190
+ .feature-card
191
+ .feature-icon "🎯"
192
+ .feature-name "Fine-Grained Rendering"
193
+ .feature-desc "No virtual DOM. Direct DOM updates via reactive effects."
194
+ '''
195
+
196
+ 'components/counter.rip': '''
197
+ # Counter Page
198
+
199
+ export Counter = component
200
+ count := @app.data.count or 0
201
+ step := 1
202
+
203
+ ~> @app.data.count = count
204
+
205
+ increment: -> count += step
206
+ decrement: -> count -= step
207
+ reset: -> count = 0
208
+
209
+ render
210
+ div
211
+ .page-header
212
+ h1.page-title "Counter"
213
+ p.page-subtitle "Reactive state — persists across reload"
214
+
215
+ .card
216
+ .counter-display "#{count}"
217
+
218
+ .counter-controls
219
+ button.btn @click: @decrement, "- #{step}"
220
+ button.btn @click: @reset, "Reset"
221
+ button.btn.btn-primary @click: @increment, "+ #{step}"
222
+
223
+ .card
224
+ .card-title "Step Size"
225
+ .btn-group
226
+ button.btn @click: (-> step = 1), "1"
227
+ button.btn @click: (-> step = 5), "5"
228
+ button.btn @click: (-> step = 10), "10"
229
+ button.btn @click: (-> step = 100), "100"
230
+ '''
231
+
232
+ 'components/todos.rip': '''
233
+ # Todos Page
234
+
235
+ export Todos = component
236
+ newTodo := ''
237
+ todos := @app.data.todos or []
238
+ nextId := (@app.data.nextId or 1)
239
+
240
+ ~> @app.data.todos = todos
241
+ ~> @app.data.nextId = nextId
242
+
243
+ remaining ~= todos.filter((t) -> not t.done).length
244
+
245
+ addTodo: ->
246
+ if newTodo.trim()
247
+ todos = [...todos, { id: nextId, text: newTodo.trim(), done: false }]
248
+ nextId++
249
+ newTodo = ''
250
+
251
+ deleteTodo: (id) ->
252
+ todos = todos.filter (t) -> t.id isnt id
253
+
254
+ clearAll: ->
255
+ todos = []
256
+
257
+ handleKey: (e) ->
258
+ if e.key is 'Enter'
259
+ @addTodo()
260
+
261
+ render
262
+ div
263
+ .page-header
264
+ h1.page-title "Todos"
265
+ p.page-subtitle "List rendering and state management"
266
+
267
+ .card
268
+ .todo-input-row
269
+ input.input type: "text", value: newTodo, placeholder: "What needs to be done?", @keydown: @handleKey
270
+ button.btn.btn-primary @click: @addTodo, "Add"
271
+
272
+ ul.todo-list
273
+ for todo in todos
274
+ li.todo-item
275
+ span.todo-text todo.text
276
+ button.todo-delete @click: (=> @deleteTodo(todo.id)), "x"
277
+
278
+ .todo-stats
279
+ span "#{remaining} remaining"
280
+ button.btn.btn-sm @click: @clearAll, "Clear all"
281
+ '''
282
+
283
+ 'components/about.rip': '''
284
+ # About Page — uses the Card component for content sections
285
+
286
+ export About = component
287
+
288
+ render
289
+ div
290
+ .page-header
291
+ h1.page-title "About Rip UI"
292
+ p.page-subtitle "Zero-build reactive web apps!"
293
+
294
+ Card title: "The Idea"
295
+ p "Traditional frameworks build and bundle on the server, then ship static artifacts. Rip UI ships the compiler to the browser instead — under 55KB on the wire. Components arrive as .rip source files, are compiled on demand, and render with fine-grained reactivity. No build step, no bundler, no configuration files."
296
+
297
+ Card title: "Two Keywords"
298
+ p "The component model adds exactly two keywords to the Rip language: component and render. Everything else — reactive state (:=), computed values (~=), effects (~>), methods, lifecycle hooks — is standard Rip syntax that already exists."
299
+
300
+ Card title: "Architecture"
301
+ p "Components are stored as source files and compiled on demand. The Router maps URLs to components using file-based conventions (like Next.js). When you navigate, the Renderer compiles the matching .rip file and mounts the resulting component. Each reactive binding creates a direct DOM effect — no virtual DOM diffing."
302
+
303
+ Card title: "Stack"
304
+ .btn-group
305
+ .stat
306
+ span "Compiler "
307
+ span.stat-value "47KB"
308
+ .stat
309
+ span "Framework "
310
+ span.stat-value "8KB"
311
+ .stat
312
+ span "Combined "
313
+ span.stat-value "52KB"
314
+ .stat
315
+ span "Build step "
316
+ span.stat-value "None"
317
+ .stat
318
+ span "Dependencies "
319
+ span.stat-value "Zero"
320
+
321
+ Card title: "Modules"
322
+ p "The framework lives in one file: ui.rip. It uses Rip's built-in reactive primitives directly — the same signals that power := and ~= in components. Deep reactive stash with path navigation. Component store for source management. File-based router mapping URLs to components. Renderer that compiles and mounts components with layout support."
323
+ '''
324
+
325
+ 'components/card.rip': '''
326
+ # Card — reusable content card component
327
+
328
+ export Card = component
329
+ title =! ""
330
+
331
+ render
332
+ div.card
333
+ if title
334
+ h3 "#{title}"
335
+ @children
336
+ '''
337
+
338
+ data:
339
+ title: 'Rip UI Demo'
340
+ </script>
341
+ </body>
342
+ </html>