@daz4126/helium 0.31.0 → 1.0.0-beta.1

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.
@@ -9,7 +9,9 @@
9
9
  "Bash(npm run test:run:*)",
10
10
  "Bash(npm link)",
11
11
  "Bash(npm install)",
12
- "Bash(npm link helium)"
12
+ "Bash(npm link helium)",
13
+ "Bash(ls:*)",
14
+ "Bash(wc:*)"
13
15
  ]
14
16
  }
15
17
  }
package/CLAUDE.md CHANGED
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
 
5
5
  ## Project Overview
6
6
 
7
- Helium is an ultra-light (~3KB minified/gzipped) reactive library that makes HTML interactive using declarative attributes. It requires no build step and works directly in the browser.
7
+ Helium is an ultra-light (~5KB minified/gzipped) reactive library that makes HTML interactive using declarative attributes. It requires no build step and works directly in the browser.
8
8
 
9
9
  ## Commands
10
10
 
@@ -0,0 +1,387 @@
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>Helium Lite Examples</title>
7
+ <style>
8
+ * { box-sizing: border-box; }
9
+
10
+ body {
11
+ font-family: system-ui, -apple-system, sans-serif;
12
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
13
+ min-height: 100vh;
14
+ margin: 0;
15
+ padding: 2rem;
16
+ }
17
+
18
+ .page {
19
+ max-width: 1200px;
20
+ margin: 0 auto;
21
+ }
22
+
23
+ h1 {
24
+ color: white;
25
+ text-align: center;
26
+ margin-bottom: 2rem;
27
+ font-size: 2.5rem;
28
+ }
29
+
30
+ .examples {
31
+ display: grid;
32
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
33
+ gap: 1.5rem;
34
+ }
35
+
36
+ .card {
37
+ background: white;
38
+ border-radius: 12px;
39
+ padding: 1.5rem;
40
+ box-shadow: 0 10px 40px rgba(0,0,0,0.2);
41
+ transition: transform 0.2s, box-shadow 0.2s;
42
+ }
43
+
44
+ .card:hover {
45
+ transform: translateY(-2px);
46
+ box-shadow: 0 15px 50px rgba(0,0,0,0.25);
47
+ }
48
+
49
+ .card.active {
50
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
51
+ border: 2px solid #667eea;
52
+ }
53
+
54
+ .card h2 {
55
+ margin: 0 0 1rem 0;
56
+ color: #333;
57
+ font-size: 1.25rem;
58
+ border-bottom: 2px solid #eee;
59
+ padding-bottom: 0.5rem;
60
+ }
61
+
62
+ button {
63
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
64
+ color: white;
65
+ border: none;
66
+ padding: 0.6rem 1.2rem;
67
+ border-radius: 6px;
68
+ cursor: pointer;
69
+ font-size: 0.95rem;
70
+ transition: transform 0.1s, box-shadow 0.2s;
71
+ }
72
+
73
+ button:hover {
74
+ transform: scale(1.02);
75
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
76
+ }
77
+
78
+ button:active {
79
+ transform: scale(0.98);
80
+ }
81
+
82
+ input, textarea, select {
83
+ width: 100%;
84
+ padding: 0.6rem;
85
+ border: 2px solid #ddd;
86
+ border-radius: 6px;
87
+ font-size: 1rem;
88
+ margin-bottom: 0.5rem;
89
+ transition: border-color 0.2s;
90
+ }
91
+
92
+ input:focus, textarea:focus, select:focus {
93
+ outline: none;
94
+ border-color: #667eea;
95
+ }
96
+
97
+ .button-group {
98
+ display: flex;
99
+ gap: 0.5rem;
100
+ flex-wrap: wrap;
101
+ }
102
+
103
+ .color-preview {
104
+ width: 100%;
105
+ height: 60px;
106
+ border-radius: 6px;
107
+ margin-top: 0.5rem;
108
+ transition: background-color 0.3s;
109
+ }
110
+
111
+ .keyboard-display {
112
+ font-family: monospace;
113
+ background: #1a1a2e;
114
+ color: #0f0;
115
+ padding: 1rem;
116
+ border-radius: 6px;
117
+ min-height: 40px;
118
+ }
119
+
120
+ .progress-bar {
121
+ width: 100%;
122
+ height: 20px;
123
+ background: #eee;
124
+ border-radius: 10px;
125
+ overflow: hidden;
126
+ }
127
+
128
+ .progress-fill {
129
+ height: 100%;
130
+ background: linear-gradient(90deg, #667eea, #764ba2);
131
+ transition: width 0.3s;
132
+ }
133
+
134
+ .tabs {
135
+ display: flex;
136
+ gap: 0;
137
+ margin-bottom: 1rem;
138
+ }
139
+
140
+ .tab {
141
+ padding: 0.5rem 1rem;
142
+ background: #eee;
143
+ border: none;
144
+ cursor: pointer;
145
+ transition: background 0.2s;
146
+ }
147
+
148
+ .tab:first-child { border-radius: 6px 0 0 6px; }
149
+ .tab:last-child { border-radius: 0 6px 6px 0; }
150
+ .tab.active { background: #667eea; color: white; }
151
+
152
+ .tab-content {
153
+ padding: 1rem;
154
+ background: #f9f9f9;
155
+ border-radius: 6px;
156
+ }
157
+
158
+ .emoji-picker {
159
+ display: flex;
160
+ gap: 0.5rem;
161
+ flex-wrap: wrap;
162
+ }
163
+
164
+ .emoji-btn {
165
+ font-size: 1.5rem;
166
+ padding: 0.5rem;
167
+ background: #f0f0f0;
168
+ border-radius: 8px;
169
+ }
170
+
171
+ .emoji-btn:hover {
172
+ background: #e0e0e0;
173
+ }
174
+
175
+ .selected-emoji {
176
+ font-size: 3rem;
177
+ text-align: center;
178
+ padding: 1rem;
179
+ }
180
+ </style>
181
+ </head>
182
+ <body>
183
+
184
+ <div class="page" data-helium @data="{ count: 0 }">
185
+ <h1>Helium Lite Examples</h1>
186
+
187
+ <div class="examples">
188
+
189
+ <!-- Counter -->
190
+ <div class="card">
191
+ <h2>Counter</h2>
192
+ <p>Simple reactive counter with increment/decrement.</p>
193
+ <div class="button-group">
194
+ <button @click="count--">-</button>
195
+ <button @click="count++">+</button>
196
+ </div>
197
+ <p>Count: <b @text="count">0</b></p>
198
+ </div>
199
+
200
+ <!-- Dynamic Classes -->
201
+ <div
202
+ @data="{ active: false }"
203
+ :class="{active: active}"
204
+ class="card"
205
+ >
206
+ <h2>Dynamic Classes</h2>
207
+ <p>Toggle the active state to change card styling.</p>
208
+ <button @click="active = !active">
209
+ Toggle (<span @text="active ? 'ON' : 'OFF'"></span>)
210
+ </button>
211
+ </div>
212
+
213
+ <!-- Two-way Binding -->
214
+ <div class="card">
215
+ <h2>Two-Way Binding</h2>
216
+ <input @bind="firstname" placeholder="First name">
217
+ <input @bind="lastname" placeholder="Last name">
218
+ <span @calculate:fullname="firstname && lastname ? firstname + ' ' + lastname : firstname || lastname || 'stranger'"></span>
219
+ <p>Hello, <b @text="fullname">stranger</b>!</p>
220
+ <button @click="firstname = '', lastname = ''">Clear</button>
221
+ </div>
222
+
223
+ <!-- Visibility Toggle -->
224
+ <div @data="{ show: true }" class="card">
225
+ <h2>Visibility Toggle</h2>
226
+ <button @click="show = !show" @text="show ? 'Hide' : 'Show'">Show</button>
227
+ <p @visible="show">Now you see me!</p>
228
+ <p @hidden="show">Now you don't!</p>
229
+ </div>
230
+
231
+ <!-- Checkbox Binding -->
232
+ <div class="card" @data="{ agreed: false, newsletter: true }">
233
+ <h2>Checkbox Binding</h2>
234
+ <label>
235
+ <input type="checkbox" @bind="agreed"> I agree to the terms
236
+ </label>
237
+ <label>
238
+ <input type="checkbox" @bind="newsletter"> Subscribe to newsletter
239
+ </label>
240
+ <p>
241
+ Terms: <b @text="agreed ? 'Accepted' : 'Not accepted'">Not accepted</b><br>
242
+ Newsletter: <b @text="newsletter ? 'Subscribed' : 'Not subscribed'">Subscribed</b>
243
+ </p>
244
+ <button @click="agreed = true, newsletter = true">Accept All</button>
245
+ </div>
246
+
247
+ <!-- Radio Buttons -->
248
+ <div class="card" @data="{ size: 'medium' }">
249
+ <h2>Radio Buttons</h2>
250
+ <label><input type="radio" name="size" value="small" @bind="size"> Small</label>
251
+ <label><input type="radio" name="size" value="medium" @bind="size"> Medium</label>
252
+ <label><input type="radio" name="size" value="large" @bind="size"> Large</label>
253
+ <p>Selected size: <b @text="size">medium</b></p>
254
+ </div>
255
+
256
+ <!-- Select Dropdown -->
257
+ <div class="card" @data="{ color: 'blue' }">
258
+ <h2>Select Dropdown</h2>
259
+ <select @bind="color">
260
+ <option value="red">Red</option>
261
+ <option value="green">Green</option>
262
+ <option value="blue">Blue</option>
263
+ <option value="purple">Purple</option>
264
+ </select>
265
+ <div class="color-preview" :style="{ backgroundColor: color }"></div>
266
+ </div>
267
+
268
+ <!-- Dynamic Style -->
269
+ <div class="card" @data="{ width: 50 }">
270
+ <h2>Dynamic Style</h2>
271
+ <input type="range" min="0" max="100" @bind="width">
272
+ <p>Width: <b @text="width">50</b>%</p>
273
+ <div class="progress-bar">
274
+ <div class="progress-fill" :style="{ width: width + '%' }"></div>
275
+ </div>
276
+ </div>
277
+
278
+ <!-- Keyboard Events -->
279
+ <div class="card" @data="{ lastKey: 'Press a key...' }">
280
+ <h2>Keyboard Events</h2>
281
+ <input placeholder="Type here..." @keydown="lastKey = $event.key" @keydown.enter="lastKey = 'ENTER pressed!'" @keydown.esc="lastKey = 'Escaped!'">
282
+ <div class="keyboard-display" @text="lastKey">Press a key...</div>
283
+ </div>
284
+
285
+ <!-- Debounced Input -->
286
+ <div class="card" @data="{ search: '', debouncedSearch: '' }">
287
+ <h2>Debounced Input</h2>
288
+ <p>Type and wait 500ms to see the debounced value update.</p>
289
+ <input placeholder="Search..." @bind="search" @input.debounce:500="debouncedSearch = search">
290
+ <p>Immediate: <b @text="search || '(empty)'"></b></p>
291
+ <p>Debounced: <b @text="debouncedSearch || '(empty)'"></b></p>
292
+ </div>
293
+
294
+ <!-- Tabs -->
295
+ <div class="card" @data="{ tab: 'one' }">
296
+ <h2>Tabs</h2>
297
+ <div class="tabs">
298
+ <button class="tab" :class="{ active: tab === 'one' }" @click="tab = 'one'">Tab 1</button>
299
+ <button class="tab" :class="{ active: tab === 'two' }" @click="tab = 'two'">Tab 2</button>
300
+ <button class="tab" :class="{ active: tab === 'three' }" @click="tab = 'three'">Tab 3</button>
301
+ </div>
302
+ <div class="tab-content" @visible="tab === 'one'">Content for Tab 1</div>
303
+ <div class="tab-content" @visible="tab === 'two'">Content for Tab 2</div>
304
+ <div class="tab-content" @visible="tab === 'three'">Content for Tab 3</div>
305
+ </div>
306
+
307
+ <!-- Element References -->
308
+ <div class="card">
309
+ <h2>Element References</h2>
310
+ <input @ref="myInput" placeholder="Focus me!">
311
+ <div class="button-group">
312
+ <button @click="$myInput.focus()">Focus Input</button>
313
+ <button @click="$myInput.value = 'Hello!'">Set Value</button>
314
+ <button @click="$myInput.value = ''">Clear</button>
315
+ </div>
316
+ </div>
317
+
318
+ <!-- Emoji Picker -->
319
+ <div class="card" @data="{ emoji: '' }">
320
+ <h2>Emoji Picker</h2>
321
+ <div class="emoji-picker">
322
+ <button class="emoji-btn" @click="emoji = '😀'">😀</button>
323
+ <button class="emoji-btn" @click="emoji = '🎉'">🎉</button>
324
+ <button class="emoji-btn" @click="emoji = '🚀'">🚀</button>
325
+ <button class="emoji-btn" @click="emoji = '💡'">💡</button>
326
+ <button class="emoji-btn" @click="emoji = '🎈'">🎈</button>
327
+ <button class="emoji-btn" @click="emoji = '❤️'">❤️</button>
328
+ </div>
329
+ <div class="selected-emoji" @text="emoji || 'Pick one!'">Pick one!</div>
330
+ </div>
331
+
332
+ <!-- Calculator -->
333
+ <div class="card" @data="{ a: 10, b: 5, op: '+' }">
334
+ <h2>Calculator</h2>
335
+ <input type="number" @bind="a" placeholder="First number">
336
+ <select @bind="op">
337
+ <option value="+">+</option>
338
+ <option value="-">-</option>
339
+ <option value="*">×</option>
340
+ <option value="/">÷</option>
341
+ </select>
342
+ <input type="number" @bind="b" placeholder="Second number">
343
+ <span @calculate:result="op === '+' ? a + b : op === '-' ? a - b : op === '*' ? a * b : op === '/' ? a / b : 0"></span>
344
+ <p style="font-size: 1.5rem; text-align: center;">
345
+ <span @text="a">10</span>
346
+ <span @text="op">+</span>
347
+ <span @text="b">5</span>
348
+ = <b @text="result">15</b>
349
+ </p>
350
+ </div>
351
+
352
+ <!-- Character Counter -->
353
+ <div class="card" @data="{ text: '', maxLength: 100 }">
354
+ <h2>Character Counter</h2>
355
+ <textarea @bind="text" placeholder="Type something..." rows="3"></textarea>
356
+ <span @calculate:remaining="maxLength - text.length"></span>
357
+ <p :style="{ color: remaining < 20 ? 'red' : 'inherit' }">
358
+ <b @text="remaining">100</b> characters remaining
359
+ </p>
360
+ </div>
361
+
362
+ <!-- Init Example -->
363
+ <div class="card" @data="{ loadTime: '' }">
364
+ <h2>Init Example</h2>
365
+ <p @init="loadTime = new Date().toLocaleTimeString()">
366
+ Page loaded at: <b @text="loadTime"></b>
367
+ </p>
368
+ <button @click="loadTime = new Date().toLocaleTimeString()">Update Time</button>
369
+ </div>
370
+
371
+ <!-- Effect Example -->
372
+ <div class="card" @data="{ clicks: 0, history: [] }">
373
+ <h2>Effect (Side Effects)</h2>
374
+ <p>Click the button and watch the history build up.</p>
375
+ <button @click="clicks++">Click Me</button>
376
+ <span @effect="clicks > 0 ? history.push('Click #' + clicks) : null"></span>
377
+ <p>Clicks: <b @text="clicks">0</b></p>
378
+ <p>History: <span @text="history.join(', ') || 'None yet'">None yet</span></p>
379
+ </div>
380
+
381
+ </div>
382
+ </div>
383
+
384
+ <script type="module" src="../helium-lite.js"></script>
385
+
386
+ </body>
387
+ </html>
@@ -245,12 +245,12 @@
245
245
  <h2>Dynamic Classes</h2>
246
246
  <p>Toggle the active state to change card styling.</p>
247
247
  <button @click="active = !active">
248
- Toggle (<span @text="active ? 'ON' : 'OFF'">OFF</span>)
248
+ Toggle (<span @text="active ? 'ON' : 'OFF'"></span>)
249
249
  </button>
250
250
  </div>
251
251
 
252
252
  <!-- Two-way Binding -->
253
- <div class="card" @data="{ firstname: '', lastname: '' }">
253
+ <div class="card">
254
254
  <h2>Two-Way Binding</h2>
255
255
  <input @bind="firstname" placeholder="First name">
256
256
  <input @bind="lastname" placeholder="Last name">
@@ -398,16 +398,17 @@
398
398
  </p>
399
399
  </div>
400
400
 
401
- <!-- Password Strength -->
402
- <div class="card" @data="{ password: '' }">
401
+ <!-- Password Strength (uses @import) -->
402
+ <div class="card" @data="{ password: '' }" @import="password-utils">
403
403
  <h2>Password Strength</h2>
404
404
  <input type="password" @bind="password" placeholder="Enter password">
405
- <span @calculate:strength="password ? (password.length < 4 ? 'Weak' : password.length < 8 ? 'Medium' : 'Strong') : 'Enter a password'"></span>
406
- <span @calculate:strengthWidth="password.length < 4 ? 33 : password.length < 8 ? 66 : 100"></span>
405
+ <span @calculate:result="checkStrength(password)"></span>
406
+ <span @calculate:tips="getPasswordTips(password)"></span>
407
407
  <div class="progress-bar">
408
- <div class="progress-fill" :style="{ width: strengthWidth + '%', background: strength === 'Strong' ? 'green' : strength === 'Medium' ? 'orange' : 'red' }"></div>
408
+ <div class="progress-fill" :style="{ width: result.percent + '%', background: strengthColor(result.label) }"></div>
409
409
  </div>
410
- <p>Strength: <b @text="strength"></b></p>
410
+ <p>Strength: <b @text="result.label"></b></p>
411
+ <p style="font-size: 0.85rem; color: #666;" @text="tips.join(' • ')"></p>
411
412
  </div>
412
413
 
413
414
  <!-- Init Example -->
@@ -449,7 +450,7 @@
449
450
  </section>
450
451
 
451
452
  <!-- Request Methods -->
452
- <section class="card" @data="{ put_response: '', delete_response: '' }">
453
+ <section class="card">
453
454
  <h2>Request Methods</h2>
454
455
  <p>PUT adds to account, DELETE (shift+click) removes.</p>
455
456
  <div class="button-group">
@@ -479,7 +480,7 @@
479
480
  </div>
480
481
  </div>
481
482
 
482
- <script type="module" src="../helium.js"></script>
483
+ <script type="module" src="../helium-csp.js"></script>
483
484
 
484
485
  </body>
485
486
  </html>
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Password strength utilities for Helium examples
3
+ * Demonstrates @import functionality
4
+ */
5
+
6
+ /**
7
+ * Check password strength based on multiple criteria
8
+ * @param {string} password - The password to check
9
+ * @returns {object} - { score: 0-4, label: string, percent: number }
10
+ */
11
+ export function checkStrength(password) {
12
+ if (!password) {
13
+ return { score: 0, label: 'Enter a password', percent: 0 };
14
+ }
15
+
16
+ let score = 0;
17
+
18
+ // Length check
19
+ if (password.length >= 8) score++;
20
+ if (password.length >= 12) score++;
21
+
22
+ // Has lowercase letter
23
+ if (/[a-z]/.test(password)) score++;
24
+
25
+ // Has uppercase letter
26
+ if (/[A-Z]/.test(password)) score++;
27
+
28
+ // Has digit
29
+ if (/[0-9]/.test(password)) score++;
30
+
31
+ // Has symbol
32
+ if (/[^a-zA-Z0-9]/.test(password)) score++;
33
+
34
+ // Map score to label (max score is 6)
35
+ const labels = ['Weak', 'Weak', 'Fair', 'Good', 'Strong', 'Strong', 'Excellent'];
36
+ const label = labels[Math.min(score, 6)];
37
+
38
+ // Percent for progress bar
39
+ const percent = Math.min(100, Math.round((score / 6) * 100));
40
+
41
+ return { score, label, percent };
42
+ }
43
+
44
+ /**
45
+ * Get color for strength level
46
+ * @param {string} label - The strength label
47
+ * @returns {string} - CSS color
48
+ */
49
+ export function strengthColor(label) {
50
+ const colors = {
51
+ 'Enter a password': '#ccc',
52
+ 'Weak': '#e53935',
53
+ 'Fair': '#ff9800',
54
+ 'Good': '#4caf50',
55
+ 'Strong': '#2e7d32',
56
+ 'Excellent': '#1b5e20'
57
+ };
58
+ return colors[label] || '#ccc';
59
+ }
60
+
61
+ /**
62
+ * Get tips for improving password
63
+ * @param {string} password - The password to check
64
+ * @returns {string[]} - Array of improvement tips
65
+ */
66
+ export function getPasswordTips(password) {
67
+ if (!password) return ['Enter a password to see tips'];
68
+
69
+ const tips = [];
70
+
71
+ if (password.length < 8) tips.push('Use at least 8 characters');
72
+ else if (password.length < 12) tips.push('Use 12+ characters for better security');
73
+
74
+ if (!/[a-z]/.test(password)) tips.push('Add lowercase letters');
75
+ if (!/[A-Z]/.test(password)) tips.push('Add uppercase letters');
76
+ if (!/[0-9]/.test(password)) tips.push('Add numbers');
77
+ if (!/[^a-zA-Z0-9]/.test(password)) tips.push('Add symbols (!@#$%...)');
78
+
79
+ return tips.length ? tips : ['Great password!'];
80
+ }