@daz4126/helium 1.0.0-beta.2 → 1.0.0-beta.4
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/CLAUDE.md +1 -3
- package/examples/index.html +13 -28
- package/examples/todo.css +109 -0
- package/examples/todo.html +46 -0
- package/examples/todo.js +36 -0
- package/helium (# Edit conflict 2026-02-04 FmuZMse #).js +629 -0
- package/helium (# Edit conflict 2026-02-04 j7PIppO #).js +629 -0
- package/helium (# Edit conflict 2026-02-04 mqpbJka #).js +629 -0
- package/helium-csp.js +2 -6
- package/helium-lite.js +216 -100
- package/helium.js +17 -32
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -105,9 +105,7 @@ const { helium, heliumTeardown } = createHelium({
|
|
|
105
105
|
createScope(ctx) {
|
|
106
106
|
// Return scope object for expression execution
|
|
107
107
|
}
|
|
108
|
-
}
|
|
109
|
-
prefix: 'my', // For data-my-* attributes
|
|
110
|
-
rootAttr: 'mylib' // For @mylib / data-mylib root element
|
|
108
|
+
}
|
|
111
109
|
});
|
|
112
110
|
```
|
|
113
111
|
|
package/examples/index.html
CHANGED
|
@@ -220,17 +220,11 @@
|
|
|
220
220
|
</head>
|
|
221
221
|
<body>
|
|
222
222
|
|
|
223
|
-
<div class="page" data-helium
|
|
223
|
+
<div class="page" data-helium>
|
|
224
224
|
<h1>Helium Examples</h1>
|
|
225
225
|
|
|
226
226
|
<div class="examples">
|
|
227
227
|
|
|
228
|
-
<!-- @data Test -->
|
|
229
|
-
<div class="card" data-he="{ word: 'Hello',number: 42 }">
|
|
230
|
-
<h2 data-he-text="word"></h2>
|
|
231
|
-
<p>Number: <b @text="number"></b></p>
|
|
232
|
-
</div>
|
|
233
|
-
|
|
234
228
|
<!-- Counter -->
|
|
235
229
|
<div class="card">
|
|
236
230
|
<h2>Counter</h2>
|
|
@@ -328,7 +322,7 @@
|
|
|
328
322
|
</div>
|
|
329
323
|
|
|
330
324
|
<!-- Debounced Input -->
|
|
331
|
-
<div class="card"
|
|
325
|
+
<div class="card">
|
|
332
326
|
<h2>Debounced Input</h2>
|
|
333
327
|
<p>Type and wait 500ms to see the debounced value update.</p>
|
|
334
328
|
<input placeholder="Search..." @bind="search" @input.debounce:500="debouncedSearch = search">
|
|
@@ -340,13 +334,13 @@
|
|
|
340
334
|
<div class="card" @data="{ tab: 'one' }">
|
|
341
335
|
<h2>Tabs</h2>
|
|
342
336
|
<div class="tabs">
|
|
343
|
-
<button class="tab" :class="{ active: tab
|
|
344
|
-
<button class="tab" :class="{ active: tab
|
|
345
|
-
<button class="tab" :class="{ active: tab
|
|
337
|
+
<button class="tab" :class="{ active: tab == 'one' }" @click="tab = 'one'">Tab 1</button>
|
|
338
|
+
<button class="tab" :class="{ active: tab == 'two' }" @click="tab = 'two'">Tab 2</button>
|
|
339
|
+
<button class="tab" :class="{ active: tab == 'three' }" @click="tab = 'three'">Tab 3</button>
|
|
346
340
|
</div>
|
|
347
|
-
<div class="tab-content" @visible="tab
|
|
348
|
-
<div class="tab-content" @visible="tab
|
|
349
|
-
<div class="tab-content" @visible="tab
|
|
341
|
+
<div class="tab-content" @visible="tab == 'one'">Content for Tab 1</div>
|
|
342
|
+
<div class="tab-content" @visible="tab == 'two'">Content for Tab 2</div>
|
|
343
|
+
<div class="tab-content" @visible="tab == 'three'">Content for Tab 3</div>
|
|
350
344
|
</div>
|
|
351
345
|
|
|
352
346
|
<!-- Element References -->
|
|
@@ -395,7 +389,7 @@
|
|
|
395
389
|
</div>
|
|
396
390
|
|
|
397
391
|
<!-- Character Counter -->
|
|
398
|
-
<div class="card" @data="{
|
|
392
|
+
<div class="card" @data="{ maxLength: 100 }">
|
|
399
393
|
<h2>Character Counter</h2>
|
|
400
394
|
<textarea @bind="text" placeholder="Type something..." rows="3"></textarea>
|
|
401
395
|
<span @calculate:remaining="maxLength - text.length"></span>
|
|
@@ -405,7 +399,7 @@
|
|
|
405
399
|
</div>
|
|
406
400
|
|
|
407
401
|
<!-- Password Strength (uses @import) -->
|
|
408
|
-
<div class="card" @
|
|
402
|
+
<div class="card" @import="https://codepen.io/daz4126/pen/xbOzGNO.js">
|
|
409
403
|
<h2>Password Strength</h2>
|
|
410
404
|
<input type="password" @bind="password" placeholder="Enter password">
|
|
411
405
|
<span @calculate:result="checkStrength(password)"></span>
|
|
@@ -418,7 +412,7 @@
|
|
|
418
412
|
</div>
|
|
419
413
|
|
|
420
414
|
<!-- Init Example -->
|
|
421
|
-
<div class="card"
|
|
415
|
+
<div class="card">
|
|
422
416
|
<h2>Init Example</h2>
|
|
423
417
|
<p @init="loadTime = new Date().toLocaleTimeString()">
|
|
424
418
|
Page loaded at: <b @text="loadTime"></b>
|
|
@@ -427,7 +421,7 @@
|
|
|
427
421
|
</div>
|
|
428
422
|
|
|
429
423
|
<!-- Effect Example -->
|
|
430
|
-
<div class="card" @data="{
|
|
424
|
+
<div class="card" @data="{ history: [] }">
|
|
431
425
|
<h2>Effect (Side Effects)</h2>
|
|
432
426
|
<p>Click the button and watch the history build up.</p>
|
|
433
427
|
<button @click="clicks++">Click Me</button>
|
|
@@ -436,21 +430,12 @@
|
|
|
436
430
|
<p>History: <span @text="history.join(', ') || 'None yet'">None yet</span></p>
|
|
437
431
|
</div>
|
|
438
432
|
|
|
439
|
-
<!-- Clipboard Copy -->
|
|
440
|
-
<div class="card" @data="{ copied: false, textToCopy: 'Hello from Helium!' }">
|
|
441
|
-
<h2>Copy to Clipboard</h2>
|
|
442
|
-
<input @bind="textToCopy" placeholder="Text to copy">
|
|
443
|
-
<button @click="navigator.clipboard.writeText(textToCopy).then(() => { copied = true; setTimeout(() => copied = false, 2000) })">
|
|
444
|
-
<span @text="copied ? 'Copied!' : 'Copy'"></span>
|
|
445
|
-
</button>
|
|
446
|
-
</div>
|
|
447
|
-
|
|
448
433
|
</div>
|
|
449
434
|
|
|
450
435
|
<!-- AJAX Examples Section -->
|
|
451
436
|
<div class="page" style="margin-top: 2rem;" @data="{
|
|
452
437
|
templates: {
|
|
453
|
-
post: data => `<section class='post'><h1>${data
|
|
438
|
+
post: data => `<section class='post'><h1>${data?.title || 'Loading...'}</h1><p>${data?.body || ''}</p></section>`
|
|
454
439
|
}
|
|
455
440
|
}">
|
|
456
441
|
<h1>AJAX Examples</h1>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
2
|
+
body {
|
|
3
|
+
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
4
|
+
background: #f5f5f5;
|
|
5
|
+
min-height: 100vh;
|
|
6
|
+
}
|
|
7
|
+
.todoapp {
|
|
8
|
+
max-width: 550px;
|
|
9
|
+
margin: 40px auto;
|
|
10
|
+
background: #fff;
|
|
11
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.2), 0 25px 50px rgba(0,0,0,0.1);
|
|
12
|
+
}
|
|
13
|
+
.todoapp h1 {
|
|
14
|
+
font-size: 80px;
|
|
15
|
+
font-weight: 200;
|
|
16
|
+
text-align: center;
|
|
17
|
+
color: #b83f45;
|
|
18
|
+
padding: 10px 0;
|
|
19
|
+
}
|
|
20
|
+
.new-todo {
|
|
21
|
+
width: 100%;
|
|
22
|
+
padding: 16px 16px 16px 20px;
|
|
23
|
+
font-size: 24px;
|
|
24
|
+
border: none;
|
|
25
|
+
border-bottom: 1px solid #ededed;
|
|
26
|
+
font-family: inherit;
|
|
27
|
+
outline: none;
|
|
28
|
+
}
|
|
29
|
+
.new-todo::placeholder { color: #d9d9d9; font-style: italic; }
|
|
30
|
+
.toggle-all-container {
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
padding: 12px 15px;
|
|
34
|
+
border-bottom: 1px solid #ededed;
|
|
35
|
+
color: #777;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
}
|
|
38
|
+
.toggle-all-container input { margin-right: 10px; }
|
|
39
|
+
.todo-list { list-style: none; }
|
|
40
|
+
.todo-item {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
padding: 15px;
|
|
44
|
+
border-bottom: 1px solid #ededed;
|
|
45
|
+
font-size: 24px;
|
|
46
|
+
}
|
|
47
|
+
.todo-item.completed .todo-text {
|
|
48
|
+
color: #949494;
|
|
49
|
+
text-decoration: line-through;
|
|
50
|
+
}
|
|
51
|
+
.toggle {
|
|
52
|
+
width: 24px;
|
|
53
|
+
height: 24px;
|
|
54
|
+
margin-right: 15px;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
flex-shrink: 0;
|
|
57
|
+
accent-color: #85cda8;
|
|
58
|
+
}
|
|
59
|
+
.todo-text { flex: 1; word-break: break-all; }
|
|
60
|
+
.destroy {
|
|
61
|
+
display: none;
|
|
62
|
+
background: none;
|
|
63
|
+
border: none;
|
|
64
|
+
color: #cc9a9a;
|
|
65
|
+
font-size: 28px;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
padding: 0 10px;
|
|
68
|
+
line-height: 1;
|
|
69
|
+
}
|
|
70
|
+
.destroy:hover { color: #af5b5e; }
|
|
71
|
+
.todo-item:hover .destroy { display: block; }
|
|
72
|
+
.footer {
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-content: space-between;
|
|
76
|
+
padding: 10px 15px;
|
|
77
|
+
color: #777;
|
|
78
|
+
font-size: 14px;
|
|
79
|
+
border-top: 1px solid #e6e6e6;
|
|
80
|
+
}
|
|
81
|
+
.filters { display: flex; gap: 3px; }
|
|
82
|
+
.filters button {
|
|
83
|
+
background: none;
|
|
84
|
+
border: 1px solid transparent;
|
|
85
|
+
padding: 3px 7px;
|
|
86
|
+
border-radius: 3px;
|
|
87
|
+
cursor: pointer;
|
|
88
|
+
font-size: 14px;
|
|
89
|
+
color: inherit;
|
|
90
|
+
font-family: inherit;
|
|
91
|
+
}
|
|
92
|
+
.filters button:hover { border-color: #db7676; }
|
|
93
|
+
.filters button.selected { border-color: #ce4646; }
|
|
94
|
+
.clear-completed {
|
|
95
|
+
background: none;
|
|
96
|
+
border: none;
|
|
97
|
+
cursor: pointer;
|
|
98
|
+
color: inherit;
|
|
99
|
+
font-size: 14px;
|
|
100
|
+
font-family: inherit;
|
|
101
|
+
}
|
|
102
|
+
.clear-completed:hover { text-decoration: underline; }
|
|
103
|
+
.info {
|
|
104
|
+
text-align: center;
|
|
105
|
+
margin: 30px 0;
|
|
106
|
+
color: #bbb;
|
|
107
|
+
font-size: 12px;
|
|
108
|
+
}
|
|
109
|
+
.info a { color: inherit; }
|
|
@@ -0,0 +1,46 @@
|
|
|
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 TodoMVC</title>
|
|
7
|
+
<link rel="stylesheet" href="todo.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
|
|
11
|
+
<section class="todoapp" data-helium @local-storage="helium-todos" @import="todo"
|
|
12
|
+
@data="{ todos: [], newTodo: '', filter: 'all', nextId: 1 }">
|
|
13
|
+
|
|
14
|
+
<span hidden @calculate:remaining="todos.filter(t => !t.completed).length"></span>
|
|
15
|
+
<span hidden @calculate:filtered="filter === 'all' ? todos : filter === 'active' ? todos.filter(t => !t.completed) : todos.filter(t => t.completed)"></span>
|
|
16
|
+
|
|
17
|
+
<header>
|
|
18
|
+
<h1>todos</h1>
|
|
19
|
+
<input class="new-todo" @bind="newTodo" @keydown.enter="addTodo($data)" placeholder="What needs to be done?" autofocus>
|
|
20
|
+
</header>
|
|
21
|
+
|
|
22
|
+
<section @visible="todos.length > 0">
|
|
23
|
+
<label class="toggle-all-container">
|
|
24
|
+
<input type="checkbox" @click="toggleAll($data)" :checked="todos.length > 0 && todos.every(t => t.completed)">
|
|
25
|
+
Mark all as complete
|
|
26
|
+
</label>
|
|
27
|
+
<ul class="todo-list" @html="renderTodos(filtered)"></ul>
|
|
28
|
+
</section>
|
|
29
|
+
|
|
30
|
+
<footer class="footer" @visible="todos.length > 0">
|
|
31
|
+
<span @text="remaining + (remaining === 1 ? ' item' : ' items') + ' left'"></span>
|
|
32
|
+
<nav class="filters">
|
|
33
|
+
<button :class="{ selected: filter === 'all' }" @click="filter = 'all'">All</button>
|
|
34
|
+
<button :class="{ selected: filter === 'active' }" @click="filter = 'active'">Active</button>
|
|
35
|
+
<button :class="{ selected: filter === 'completed' }" @click="filter = 'completed'">Completed</button>
|
|
36
|
+
</nav>
|
|
37
|
+
<button class="clear-completed" @click="clearCompleted($data)" @visible="todos.some(t => t.completed)">Clear completed</button>
|
|
38
|
+
</footer>
|
|
39
|
+
|
|
40
|
+
</section>
|
|
41
|
+
|
|
42
|
+
<p class="info">Built with <a href="https://github.com/daz-codes/helium">Helium</a></p>
|
|
43
|
+
|
|
44
|
+
<script type="module" src="../helium.js"></script>
|
|
45
|
+
</body>
|
|
46
|
+
</html>
|
package/examples/todo.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function esc(s) {
|
|
2
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function renderTodos(list) {
|
|
6
|
+
return list.map(t => `<li class='todo-item${t.completed ? ' completed' : ''}'>
|
|
7
|
+
<input type='checkbox' class='toggle'${t.completed ? ' checked' : ''} @click='toggleTodo($data, ${t.id})'>
|
|
8
|
+
<span class='todo-text'>${esc(t.text)}</span>
|
|
9
|
+
<button class='destroy' @click='removeTodo($data, ${t.id})'>×</button>
|
|
10
|
+
</li>`).join('');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function addTodo($data) {
|
|
14
|
+
const text = $data.newTodo.trim();
|
|
15
|
+
if (text) {
|
|
16
|
+
$data.todos = [...$data.todos, { id: $data.nextId++, text, completed: false }];
|
|
17
|
+
$data.newTodo = '';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function toggleTodo($data, id) {
|
|
22
|
+
$data.todos = $data.todos.map(t => t.id === id ? { ...t, completed: !t.completed } : t);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function removeTodo($data, id) {
|
|
26
|
+
$data.todos = $data.todos.filter(t => t.id !== id);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function toggleAll($data) {
|
|
30
|
+
const allDone = $data.todos.every(t => t.completed);
|
|
31
|
+
$data.todos = $data.todos.map(t => ({ ...t, completed: !allDone }));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function clearCompleted($data) {
|
|
35
|
+
$data.todos = $data.todos.filter(t => !t.completed);
|
|
36
|
+
}
|