-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdemo.html
More file actions
315 lines (299 loc) · 12.5 KB
/
Copy pathdemo.html
File metadata and controls
315 lines (299 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>moxi.js demo</title>
<style>
:root {
--fg: #1a1a1a;
--muted: #666;
--bg: #fafafa;
--card: #fff;
--border: #e0e0e0;
--accent: #2563eb;
--accent-bg: #dbeafe;
--code-bg: #f4f4f5;
}
* { box-sizing: border-box; }
body {
font: 15px/1.5 -apple-system, BlinkMacSystemFont, sans-serif;
color: var(--fg);
background: var(--bg);
max-width: 780px;
margin: 2em auto;
padding: 0 1em;
}
h1 { font-weight: 700; margin-bottom: 0.2em; }
h1 small { color: var(--muted); font-weight: 400; font-size: 0.6em; }
h2 { margin-top: 2em; font-size: 1.15em; border-bottom: 1px solid var(--border); padding-bottom: 0.3em; }
.intro { color: var(--muted); }
.demo {
background: var(--card);
border: 1px solid var(--border);
border-radius: 6px;
padding: 1em 1.2em;
margin: 1em 0;
}
.demo .play { margin-bottom: 0.8em; }
.demo pre {
background: var(--code-bg);
padding: 0.7em 0.9em;
border-radius: 4px;
font-size: 13px;
overflow-x: auto;
margin: 0;
}
button, input[type=button] {
background: var(--accent);
color: #fff;
border: 0;
border-radius: 4px;
padding: 0.4em 0.9em;
font: inherit;
cursor: pointer;
}
button:hover { filter: brightness(1.1); }
button:disabled { opacity: 0.4; cursor: not-allowed; filter: none; }
input[type=text], input[type=search], input[type=email] {
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.4em 0.6em;
font: inherit;
min-width: 200px;
}
output { font-family: ui-monospace, monospace; color: var(--accent); }
.tab {
background: var(--card);
color: var(--fg);
border: 1px solid var(--border);
margin-right: 0.3em;
}
.tab.active { background: var(--accent); color: #fff; border-color: var(--accent); }
#panel {
background: var(--accent-bg);
padding: 1em;
margin-top: 0.8em;
border-radius: 4px;
max-height: 0;
overflow: hidden;
transition: max-height 0.25s ease;
}
#panel.open { max-height: 200px; }
#menu {
background: var(--card);
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.8em;
margin-top: 0.5em;
display: inline-block;
}
#menu[hidden] { display: none; }
::view-transition-old(panel),
::view-transition-new(panel) { animation-duration: 0.25s; }
.demo code { background: var(--code-bg); padding: 1px 5px; border-radius: 3px; font-size: 90%; }
</style>
</head>
<body>
<h1>🥊 moxi.js <small>demo</small></h1>
<h2>1. Reactive output</h2>
<div class="demo">
<div class="play">
<input id="name" placeholder="type your name">
<output live="this.innerText = q('#name').value ? 'hello ' + q('#name').value : ''"></output>
</div>
<pre><input id="name" placeholder="type your name">
<output live="this.innerText = q('#name').value ? 'hello ' + q('#name').value : ''"></output></pre>
</div>
<h2>2. Counter with per-element state</h2>
<div class="demo">
<div class="play">
<button on-init="this.count = 0"
on-click="this.count++; q('next output').value = this.count">click me</button>
<output>0</output>
</div>
<pre><button on-init="this.count = 0"
on-click="this.count++; q('next output').value = this.count">click me</button>
<output>0</output></pre>
</div>
<h2>3. Active tab with <code>take()</code></h2>
<div class="demo">
<div class="play">
<button class="tab active" on-click="q(this).take('active', '.tab'); q('#tab-out').innerText = this.innerText">One</button>
<button class="tab" on-click="q(this).take('active', '.tab'); q('#tab-out').innerText = this.innerText">Two</button>
<button class="tab" on-click="q(this).take('active', '.tab'); q('#tab-out').innerText = this.innerText">Three</button>
<p>selected: <output id="tab-out">One</output></p>
</div>
<pre><button class="tab active" on-click="q(this).take('active', '.tab'); ...">One</button>
<button class="tab" on-click="q(this).take('active', '.tab'); ...">Two</button>
<button class="tab" on-click="q(this).take('active', '.tab'); ...">Three</button></pre>
</div>
<h2>4. Debounced search</h2>
<div class="demo">
<div class="play">
<input type="search" id="q"
on-input="await debounce(250); q('next output').innerText = this.value ? 'searching: ' + this.value : ''">
<output></output>
</div>
<pre><input type="search" on-input="await debounce(250); q('next output').innerText = 'searching: ' + this.value"></pre>
<p style="color:var(--muted);font-size:13px;margin:0.4em 0 0">Only the latest keystroke after a 250ms pause produces an update.</p>
</div>
<h2>5. View transition on toggle</h2>
<div class="demo">
<div class="play">
<button on-click="transition(() => q('#panel').classList.toggle('open'))">toggle panel</button>
<div id="panel" style="view-transition-name: panel">
The panel slides open and closed, animated by the browser's View Transition API when available.
</div>
</div>
<pre><button on-click="transition(() => q('#panel').classList.toggle('open'))">toggle</button>
<div id="panel">...</div></pre>
</div>
<h2>6. Click-outside-to-dismiss</h2>
<div class="demo">
<div class="play">
<button on-click.stop="q('#menu').hidden = false">open menu</button>
<div id="menu" hidden on-click.outside="this.hidden = true">
<em>Click outside this menu to dismiss it.</em>
</div>
</div>
<pre><button on-click.stop="q('#menu').hidden = false">open menu</button>
<div id="menu" hidden on-click.outside="this.hidden = true">...</div></pre>
<p style="color:var(--muted);font-size:13px;margin:0.4em 0 0"><code>.stop</code> on the opener keeps the same click from bubbling to the <code>.outside</code> listener, which would otherwise dismiss the menu immediately.</p>
</div>
<h2>7. Parent listens for child emits</h2>
<div class="demo">
<div class="play">
<div on-confirm="q('.result in this').innerText = 'got: ' + event.detail">
<button on-click="trigger('confirm', 'yes')">yes</button>
<button on-click="trigger('confirm', 'no')">no</button>
<p class="result" style="margin-top:0.5em;color:var(--muted)"></p>
</div>
</div>
<pre><div on-confirm="q('.result in this').innerText = 'got: ' + event.detail">
<button on-click="trigger('confirm', 'yes')">yes</button>
<button on-click="trigger('confirm', 'no')">no</button>
<p class="result"></p>
</div></pre>
</div>
<h2>8. Live count of a dynamic list</h2>
<div class="demo">
<div class="play">
<button on-click="q('#items').insert('end', `<li class='item'>item ${q('.item').count + 1}</li>`)">add item</button>
<button on-click="q('#items').lastElementChild?.remove()">remove last</button>
<p><output live="this.innerText = q('.item').count + ' items'"></output></p>
<ul id="items">
<li class="item">item 1</li>
<li class="item">item 2</li>
</ul>
</div>
<pre><button on-click="q('#items').insert('end', `<li class='item'>item ${q('.item').count + 1}</li>`)">add item</button>
<output live="this.innerText = q('.item').count + ' items'"></output>
<ul id="items">
<li class="item">item 1</li>
<li class="item">item 2</li>
</ul></pre>
<p style="color:var(--muted);font-size:13px;margin:0.4em 0 0">The count watches <code>.item</code>, so new items need the same class.</p>
</div>
<h2>9. Form validity driving a button</h2>
<div class="demo">
<div class="play">
<form id="signup" onsubmit="event.preventDefault(); alert('submitted!')">
<input type="email" required placeholder="email">
<input type="text" required minlength="3" placeholder="username (3+ chars)">
<button live="this.disabled = !q('#signup').checkValidity()">submit</button>
</form>
</div>
<pre><form id="signup">
<input type="email" required placeholder="email">
<input type="text" required minlength="3" placeholder="username">
<button live="this.disabled = !q('#signup').checkValidity()">submit</button>
</form></pre>
</div>
<h2>10. <code>.halt</code> on a form submit</h2>
<div class="demo">
<div class="play">
<form on-submit.halt="q('next output').innerText = 'caught: ' + new FormData(this).get('q')">
<input name="q" type="text" placeholder="type and press enter">
<button type="submit">submit</button>
</form>
<output></output>
</div>
<pre><form on-submit.halt="q('next output').innerText = 'caught: ' + new FormData(this).get('q')">
<input name="q" placeholder="type and press enter">
<button type="submit">submit</button>
</form></pre>
</div>
<h2>11. Pairing with fixi: inject <code>confirm()</code> on a delete</h2>
<div class="demo">
<div class="play">
<button fx-action="/things/42" fx-method="DELETE" fx-swap="none"
on-fx:config="cfg.confirm = () => confirm('Delete this thing?')">
delete thing
</button>
</div>
<pre><button fx-action="/things/42" fx-method="DELETE"
on-fx:config="cfg.confirm = () => confirm('Delete this thing?')">
delete thing
</button></pre>
<p style="color:var(--muted);font-size:13px;margin:0.4em 0 0">moxi sets <code>cfg.confirm</code> on fixi's request config (via the bare-name <code>cfg</code> exposed by moxi); fixi pauses to call it before issuing the request.</p>
</div>
<h2>12. Pairing with fixi: react to <code>fx:after</code></h2>
<div class="demo">
<section id="afterdemo" style="padding:0.5em;border:1px dashed var(--border);border-radius:4px;transition:background-color 0.3s">
<button fx-action="/data" fx-target="#after-out" fx-swap="innerHTML"
on-fx:after="q('closest section').style.background = '#dcfce7'">
fetch into the panel
</button>
<div id="after-out" style="margin-top:0.5em;color:var(--muted)">(empty)</div>
</section>
<pre><section>
<button fx-action="/data" fx-target="#out" fx-swap="innerHTML"
on-fx:after="q('closest section').style.background = '#dcfce7'">
fetch
</button>
<div id="out"></div>
</section></pre>
<p style="color:var(--muted);font-size:13px;margin:0.4em 0 0">After fixi swaps the response into the target, moxi's <code>on-fx:after</code> handler runs and tints the surrounding section. The <code>q('closest section')</code> walks up to the nearest enclosing <code><section></code>.</p>
</div>
<h2>13. Pairing with fixi: trigger a fixi request from moxi</h2>
<div class="demo">
<div class="play">
<button on-click="q('#refresh-target').trigger('refresh')">reload</button>
<div id="refresh-target" fx-action="/data" fx-trigger="refresh" fx-swap="innerHTML"
style="margin-top:0.5em;padding:0.4em;border:1px dashed var(--border);border-radius:4px;color:var(--muted)">
click reload to populate
</div>
</div>
<pre><button on-click="q('#target').trigger('refresh')">reload</button>
<div id="target" fx-action="/data" fx-trigger="refresh">...</div></pre>
<p style="color:var(--muted);font-size:13px;margin:0.4em 0 0">moxi fires a custom <code>refresh</code> event from the target; fixi's <code>fx-trigger="refresh"</code> picks it up and issues the request.</p>
</div>
<h2>14. Refresh <code>live</code> from outside the DOM</h2>
<div class="demo">
<div class="play">
<button on-init="this.n = 0"
on-click="this.n++; trigger('refresh')">click</button>
<output live="this.innerText = q('prev button').n + ' clicks'"></output>
</div>
<pre><button on-init="this.n = 0"
on-click="this.n++; trigger('refresh')">click</button>
<output live="this.innerText = q('prev button').n + ' clicks'"></output></pre>
<p style="color:var(--muted);font-size:13px;margin:0.4em 0 0">moxi recomputes <code>live</code> blocks on DOM input/change/mutation. For state changes that don't touch the DOM (here, a JS property on the button), dispatch a <code>refresh</code> event from anywhere to re-run all live blocks.</p>
</div>
<script src="moxi.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fixi-js@0.9.4/fixi.js"
integrity="sha256-rPuE58bCyMrZ37o4bByX4epgBA1bAeTylp7TxOXVh90="
crossorigin="anonymous"></script>
<script>
// Mock fixi's fetch so the fixi-paired demos (sections 11-13) show observable
// behavior without needing a server. Real apps don't do this — fixi's default
// fetch hits the URL named by fx-action.
document.addEventListener('fx:config', (e) => {
e.detail.cfg.fetch = () => Promise.resolve(new Response(
'<em style="color:#16a34a">(mock response from ' + e.detail.cfg.action + ')</em>',
{status: 200, headers: {'Content-Type': 'text/html'}}
))
})
</script>
</body>
</html>