@daz4126/helium 1.0.0-beta.5 → 1.0.0-rc.2

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 CHANGED
@@ -18,24 +18,24 @@ It's really simple to use - just sprinkle the magic @attributes into your HTML a
18
18
 
19
19
  Helium is designed for developers who want:
20
20
 
21
- - **Lightweight** - Just over 3KB minified and gzipped
21
+ - **Lightweight** - About 3.9–10.9KB minified and gzipped, depending on the build
22
22
  - **Powerful** - Declarative JavaScript in your HTML
23
23
  - **Zero build step** - Works directly in the browser with no compiling
24
24
  - **Easy to learn** - If you know HTML and basic JavaScript, you're ready
25
25
 
26
26
  ## Versions
27
27
 
28
- Helium comes in three versions so you can pick the right balance of features and size for your project:
28
+ Helium comes in three core versions, with SSE available as an optional add-on, so you can pick the right balance of features and size for your project:
29
29
 
30
30
  ### Helium (Standard)
31
31
 
32
32
  The full-featured version with everything included.
33
33
 
34
- - All directives (`@text`, `@html`, `@bind`, `@data`, `@ref`, `@calculate`, `@effect`, `@init`, etc.)
34
+ - All directives (`@text`, `@html`, `@for`, `@bind`, `@data`, `@scope`, `@ref`, `@calculate`, `@effect`, `@init`, etc.)
35
35
  - Event handlers with modifiers
36
36
  - HTTP requests (`@get`, `@post`, `@put`, `@patch`, `@delete`)
37
37
  - Module imports (`@import`)
38
- - Server-Sent Events (SSE)
38
+ - Optional Server-Sent Events (SSE) add-on
39
39
  - Turbo/Hotwire integration
40
40
 
41
41
  **Best for:** Most projects where you want the full power of Helium without worrying about CSP restrictions.
@@ -50,7 +50,8 @@ A slimmed-down version with just the core reactivity features.
50
50
 
51
51
  - Core directives (`@text`, `@html`, `@bind`, `@data`, `@ref`, `@calculate`, `@effect`, `@init`)
52
52
  - Event handlers with modifiers
53
- - No HTTP requests, imports, SSE, or Turbo integration
53
+ - One global state namespace
54
+ - No `@scope`, keyed `@for`, HTTP requests, imports, SSE, or Turbo integration
54
55
 
55
56
  **Best for:** Simple interactive pages where you only need reactivity and don't need server communication or module imports.
56
57
 
@@ -60,11 +61,38 @@ import helium from "@daz4126/helium/lite"
60
61
 
61
62
  ### Helium CSP
62
63
 
63
- A Content Security Policy safe version that doesn't require `unsafe-eval`. It uses [jexpr](https://github.com/nicolo-ribaudo/jexpr) as its expression engine instead of `new Function()`, while keeping the full feature set.
64
+ A Content Security Policy safe version that doesn't require `unsafe-eval`. It uses [jexpr](https://github.com/nicolo-ribaudo/jexpr) as its expression engine instead of `new Function()`.
64
65
 
65
- - All the same features as the standard version
66
+ - All the same directives and HTTP/Turbo/`@import` features as the standard version
67
+ - Optional Server-Sent Events (SSE) add-on
68
+ - `&&` / `||` / `??` short-circuit correctly, and optional chaining (`a?.b`) is supported
66
69
  - CSP-compliant — no `unsafe-eval` needed
67
70
 
71
+ Because expressions run through a custom parser rather than the JS engine, a couple of syntax features differ from the standard build:
72
+
73
+ - No `new` keyword — use the `$Date(...)` and `$FormData(...)` helpers instead
74
+ - Expressions are single expressions, not arbitrary statements
75
+
76
+ Expressions also run in a **restricted scope**: only side-effect-free builtins
77
+ (`Math`, `Date`, `JSON`, `Object`, `Array`, `parseInt`, `URL`, and friends) are
78
+ available. Capability-bearing globals — `window`, `document`, `fetch`,
79
+ `localStorage`, `sessionStorage`, `navigator`, `location`, `history`, timers,
80
+ and dialogs — are not reachable from expressions, so markup injected by an
81
+ attacker can't use them to read cookies or storage. If a page legitimately
82
+ needs one, opt in explicitly by passing the reference:
83
+
84
+ ```javascript
85
+ import helium, { allowGlobals } from "@daz4126/helium/csp";
86
+
87
+ allowGlobals({ localStorage, fetch }); // expose to expressions
88
+ allowGlobals({ fetch: undefined }); // revoke again
89
+ ```
90
+
91
+ (`helium.allowGlobals(...)` works too when using the global script build.)
92
+ Note this reduces the attack surface but is not a sandbox: the `$get`/`$post`
93
+ helpers and `$`/`$el`/`$html` magics remain available to any expression that
94
+ can run at all.
95
+
68
96
  **Best for:** Projects with strict Content Security Policies that prohibit `unsafe-eval`.
69
97
 
70
98
  ```javascript
@@ -76,12 +104,23 @@ import helium from "@daz4126/helium/csp"
76
104
  | Feature | Standard | Lite | CSP |
77
105
  |---------|----------|------|-----|
78
106
  | Core directives | ✅ | ✅ | ✅ |
107
+ | Element-local `@scope` | ✅ | ❌ | ✅ |
108
+ | Keyed `@for` | ✅ | ❌ | ✅ |
79
109
  | Event handlers | ✅ | ✅ | ✅ |
80
110
  | HTTP requests | ✅ | ❌ | ✅ |
81
111
  | `@import` | ✅ | ❌ | ✅ |
82
- | SSE | ✅ | ❌ | ✅ |
112
+ | SSE | Add-on | ❌ | Add-on |
83
113
  | Turbo integration | ✅ | ❌ | ✅ |
84
114
  | CSP safe | ❌ | ❌ | ✅ |
115
+ | Approx. min+gzip size | 6.49KB | 3.93KB | 10.56KB |
116
+
117
+ Sizes are enforced against the production Terser builds with gzip level 9. The
118
+ CSP figure includes its expression parser. Adding SSE produces 6.86KB Standard
119
+ and 10.94KB CSP bundles.
120
+
121
+ Lite is generated from the same global-state reactive core as Standard. It has
122
+ identical deep/shared reactivity, lifecycle, binding, and cleanup behavior, but
123
+ intentionally excludes element-local scopes and Standard's other extensions.
85
124
 
86
125
  ## Installation
87
126
 
@@ -104,6 +143,11 @@ Just import from the CDN in a script tag directly in your HTML page:
104
143
  <script type="module">
105
144
  import helium from 'https://cdn.jsdelivr.net/gh/daz-codes/helium/helium-csp.js';
106
145
  </script>
146
+
147
+ <!-- Standard + SSE -->
148
+ <script type="module">
149
+ import helium from 'https://cdn.jsdelivr.net/gh/daz-codes/helium/helium-sse.js';
150
+ </script>
107
151
  ```
108
152
 
109
153
  ### NPM
@@ -115,14 +159,57 @@ npm install @daz4126/helium
115
159
  Then import the version you need:
116
160
 
117
161
  ```javascript
118
- import helium from "@daz4126/helium" // Standard
119
- import helium from "@daz4126/helium/lite" // Lite
120
- import helium from "@daz4126/helium/csp" // CSP
162
+ import helium from "@daz4126/helium" // Standard
163
+ import helium from "@daz4126/helium/lite" // Lite
164
+ import helium from "@daz4126/helium/csp" // CSP
165
+ import helium from "@daz4126/helium/sse" // Standard + SSE
166
+ import helium from "@daz4126/helium/csp/sse" // CSP + SSE
121
167
  ```
122
168
 
169
+ For a pre-minified, self-contained bundle, append `/min` to an entry point:
170
+
171
+ ```javascript
172
+ import helium from "@daz4126/helium/min"
173
+ import helium from "@daz4126/helium/sse/min"
174
+ ```
175
+
176
+ The source entry points remain browser-ready ES modules, so consuming Helium
177
+ does not require a build step.
178
+
123
179
  ### Automatic Initialization
124
180
 
125
- Helium automatically initializes on `DOMContentLoaded`, so you typically don't need to call `helium()` manually unless you're providing default values or functions.
181
+ Helium automatically initializes on `DOMContentLoaded`, so you typically don't need to call `helium()` manually. Calling it manually to provide initial values or functions suppresses the pending automatic initialization, so your state is not replaced when the DOM becomes ready.
182
+
183
+ ### Explicit Mounting
184
+
185
+ Use `helium.mount()` when a page has multiple independent Helium roots. It
186
+ accepts a selector or an element and returns the root's state plus an idempotent
187
+ `unmount()` function:
188
+
189
+ ```html
190
+ <section id="counter-a">
191
+ <button @click="count++">Increment</button>
192
+ <span @text="count"></span>
193
+ </section>
194
+
195
+ <section id="counter-b">
196
+ <button @click="count++">Increment</button>
197
+ <span @text="count"></span>
198
+ </section>
199
+ ```
200
+
201
+ ```javascript
202
+ const a = await helium.mount("#counter-a", { count: 0 })
203
+ const b = await helium.mount("#counter-b", { count: 10 })
204
+
205
+ a.state.count++ // Updates only #counter-a
206
+ a.unmount() // Safe to call more than once
207
+ ```
208
+
209
+ Explicit roots have independent state, bindings, observers, listeners, refs,
210
+ and cleanup. Calling `mount()` suppresses the pending automatic initialization;
211
+ existing single-root pages can continue relying on auto-initialization or
212
+ calling `helium(initialState)` directly.
126
213
 
127
214
  ## Helium Attributes
128
215
 
@@ -130,7 +217,10 @@ Helium uses custom attributes to add interactivity to HTML elements. To identify
130
217
 
131
218
  ### @helium
132
219
 
133
- This attribute sets the root element. Helium attributes can only be used on this element and its children. If not set then it defaults to `document.body`.
220
+ This attribute sets the automatically initialized root element. Helium
221
+ attributes are processed on that element and its children. If it is omitted,
222
+ automatic initialization defaults to `document.body`. Explicit
223
+ `helium.mount()` roots do not need this attribute.
134
224
 
135
225
  ```html
136
226
  <div @helium>
@@ -173,6 +263,43 @@ Similar to `@text`, but inserts HTML content into the element's innerHTML. Suppo
173
263
 
174
264
  **Alias:** `data-he-html`
175
265
 
266
+ ### @for with :key
267
+
268
+ Standard and CSP can render arrays and other iterables as keyed DOM rows. Put
269
+ `@for` and `:key` on a `<template>`; Lite deliberately excludes this feature.
270
+
271
+ ```html
272
+ <ul>
273
+ <template @for="item, index in items" :key="item.id">
274
+ <li>
275
+ <span @text="index + 1"></span>.
276
+ <span @text="item.name"></span>
277
+ <button @click="item.done = !item.done">Toggle</button>
278
+ </li>
279
+ </template>
280
+ </ul>
281
+ ```
282
+
283
+ The optional second alias is the current zero-based index:
284
+
285
+ ```html
286
+ <template @for="item in items" :key="item.id">
287
+ <!-- item is available to every Helium expression in this row -->
288
+ </template>
289
+ ```
290
+
291
+ Keys must be stable and unique. When the collection changes, Helium moves and
292
+ reuses rows with matching keys, creates new rows, and removes missing rows. This
293
+ preserves element identity, focus, listeners, and `@init` lifecycle state while
294
+ still updating `item`, `index`, and normal root-state dependencies. Cleanup
295
+ functions returned by `@init` run when their keyed row is removed.
296
+
297
+ `@html="items.map(...).join('')"` remains useful for simple generated markup,
298
+ but keyed `@for` avoids HTML-string interpolation and lets each row use normal
299
+ `@text`, dynamic attributes, events, and other directives.
300
+
301
+ **Alias:** `data-he-for` (the key remains `:key`)
302
+
176
303
  ### @bind
177
304
 
178
305
  Creates a 2-way binding between an input element's value and a variable. Whatever is entered in the following input field will be stored as a variable called name:
@@ -187,6 +314,13 @@ Works with:
187
314
  - Radio buttons (binds to `value`, checking the one that matches)
188
315
  - Select elements (binds to `value`)
189
316
 
317
+ Nested paths work too — the parent object must already exist in state:
318
+
319
+ ```html
320
+ <input @bind="user.name">
321
+ <p @text="user.name"></p>
322
+ ```
323
+
190
324
  **Examples:**
191
325
  ```html
192
326
  <!-- Text input -->
@@ -209,7 +343,38 @@ Works with:
209
343
  </select>
210
344
  ```
211
345
 
212
- **Alias:** `data-he-bind`
346
+ **`.number` modifier:** append `.number` to coerce the bound value to a number
347
+ (handy for `type="number"` inputs, which otherwise bind as strings). An empty
348
+ field stays `""`, and non-numeric input is left untouched so partial typing
349
+ still works.
350
+
351
+ ```html
352
+ <input type="number" @bind.number="age">
353
+ <p @text="age + 1"></p> <!-- numeric addition, not string concatenation -->
354
+ ```
355
+
356
+ **`.trim` modifier:** trim leading and trailing whitespace before storing user
357
+ input in state:
358
+
359
+ ```html
360
+ <input @bind.trim="name">
361
+ ```
362
+
363
+ **`.lazy` modifier:** update state on the element's `change` event rather than
364
+ on every `input` event. For text fields this normally means when the edit is
365
+ committed or the field loses focus:
366
+
367
+ ```html
368
+ <input @bind.lazy="search">
369
+ ```
370
+
371
+ Modifiers can be combined in any order:
372
+
373
+ ```html
374
+ <input @bind.lazy.trim.number="amount">
375
+ ```
376
+
377
+ **Alias:** `data-he-bind` (for example `data-he-bind.lazy.trim`)
213
378
 
214
379
  ### @hidden & @visible
215
380
 
@@ -241,6 +406,50 @@ You can then use these variables in other Helium attributes:
241
406
 
242
407
  **Alias:** `data-he-data`
243
408
 
409
+ ### @scope (Standard/CSP)
410
+
411
+ Creates reactive state owned by an element and its descendants. Scoped names
412
+ shadow root state without changing the object returned by `helium()`:
413
+
414
+ ```html
415
+ <main @data="{ count: 100 }">
416
+ <section @scope="{ count: 0, open: false }">
417
+ <button @click="count++">Increment local count</button>
418
+ <button @click="open = !open">Toggle</button>
419
+ <p @text="count"></p>
420
+ <p @visible="open">Local content</p>
421
+ </section>
422
+
423
+ <p @text="count"></p> <!-- Still 100 -->
424
+ </main>
425
+ ```
426
+
427
+ The scope expression runs once in its parent context, so defaults can use outer
428
+ values. Nested scopes inherit outer names, and assignment updates the nearest
429
+ scope that declares that name:
430
+
431
+ ```html
432
+ <div @scope="{ count: startingCount, label: 'outer' }">
433
+ <div @scope="{ label: 'inner' }">
434
+ <button @click="count++">Updates the outer local count</button>
435
+ <span @text="label"></span> <!-- inner -->
436
+ </div>
437
+ </div>
438
+ ```
439
+
440
+ Declare every name that should remain local. Assigning an undeclared name keeps
441
+ the existing Helium behavior and writes to root state. The same applies to
442
+ `@bind` and `@calculate` targets: declare their target names in `@scope` when
443
+ they should be local. Inside the subtree, `$data` is the scoped state view.
444
+ Local values are not included in root `@local-storage` persistence.
445
+
446
+ `@data` continues to merge into root state; it has not changed semantics.
447
+
448
+ **Alias:** `data-he-scope`
449
+
450
+ `@scope` is intentionally excluded from Lite, which always uses one global
451
+ state namespace per mounted root.
452
+
244
453
  ### @ref
245
454
 
246
455
  Creates a reference to the element that can be used in JavaScript expressions. References are prefixed with `$` when accessed.
@@ -266,6 +475,22 @@ A JavaScript expression that will run once when Helium initializes. Useful for s
266
475
  <div @init="console.log('Helium initialized!')"></div>
267
476
  ```
268
477
 
478
+ If the expression returns a function, Helium calls it when the element is removed or when Helium is torn down. This is useful for releasing timers, observers, subscriptions, and other resources:
479
+
480
+ ```html
481
+ <div @init="start_clock($el, $data)"></div>
482
+ ```
483
+
484
+ ```js
485
+ function start_clock(element, data) {
486
+ const timer = setInterval(() => {
487
+ element.textContent = new Date().toLocaleTimeString()
488
+ }, 1000)
489
+
490
+ return () => clearInterval(timer)
491
+ }
492
+ ```
493
+
269
494
  **Alias:** `data-he-init`
270
495
 
271
496
  ### @calculate
@@ -459,7 +684,7 @@ You can add modifiers by appending them with a dot (`.`) after the event name:
459
684
  <div @keydown.document.esc="closeModal()">Press ESC anywhere</div>
460
685
  ```
461
686
 
462
- **Alias:** Prepend the event name with `data-he-on`, for example `data-he-onclick="count++"`
687
+ **Alias:** Prepend the event name with `data-he-`, for example `data-he-click="count++"`
463
688
 
464
689
  ## HTTP Requests
465
690
 
@@ -643,6 +868,44 @@ Additional fetch options (as an object):
643
868
  - `data-he-loading`
644
869
  - `data-he-options`
645
870
 
871
+ ### Server-Sent Events (optional)
872
+
873
+ SSE parsing is kept out of the core build. Select the add-on entry point when a
874
+ page needs it:
875
+
876
+ ```javascript
877
+ import helium from "@daz4126/helium/sse"
878
+ // Strict CSP: import helium from "@daz4126/helium/csp/sse"
879
+ ```
880
+
881
+ Use the normal HTTP directives. An explicit `@target` is applied to every SSE
882
+ message:
883
+
884
+ ```html
885
+ <button @get="/api/events" @target="#events:append">Connect</button>
886
+ <div id="events"></div>
887
+ ```
888
+
889
+ When `@target` is omitted, an SSE `event:` field can name a CSS selector or a
890
+ state property:
891
+
892
+ ```text
893
+ event: #status
894
+ data: Connected
895
+ ```
896
+
897
+ Set `retryMode` and an optional initial `retry` delay through `@options` to
898
+ reconnect after the stream closes or fails. A server-supplied `retry:` field
899
+ updates the delay and `id:` is sent back as `Last-Event-ID` on reconnection.
900
+
901
+ ```html
902
+ <button @get="/api/events"
903
+ @target="#events:append"
904
+ @options="{ retryMode: 'error', retry: 3000 }">
905
+ Connect
906
+ </button>
907
+ ```
908
+
646
909
  ### Complete Example
647
910
 
648
911
  ```html
@@ -795,8 +1058,10 @@ HTTP request functions that can be called programmatically:
795
1058
 
796
1059
  The arguments are `url`,`params` (not for `$get`) and `options`. `options` is an object that can include the properties `loading`,`target`, `template`
797
1060
 
798
- ### $refs
799
- Object containing all elements marked with `@ref` (prefixed with `$`):
1061
+ ### Named refs
1062
+
1063
+ Each element marked with `@ref="name"` is available as `$name`. There is no
1064
+ separate `$refs` object:
800
1065
 
801
1066
  ```html
802
1067
  <input @ref="username">
@@ -1004,7 +1269,11 @@ The token is automatically included in POST, PUT, PATCH, and DELETE requests to
1004
1269
 
1005
1270
  ### Content Security Policy
1006
1271
 
1007
- If you're using a Content Security Policy, note that Helium uses `new Function()` to evaluate expressions. You'll need to allow `unsafe-eval` or use a build step to pre-compile expressions (coming in a future version).
1272
+ The standard and lite builds use `new Function()` and therefore require `unsafe-eval`. For a strict Content Security Policy, use the CSP build instead:
1273
+
1274
+ ```js
1275
+ import helium from "@daz4126/helium/csp"
1276
+ ```
1008
1277
 
1009
1278
  ## Best Practices
1010
1279
 
@@ -1093,22 +1362,19 @@ If you're using a Content Security Policy, note that Helium uses `new Function()
1093
1362
 
1094
1363
  ### Common Pitfalls
1095
1364
 
1096
- **❌ Don't mutate arrays/objects without triggering reactivity:**
1365
+ **Arrays and objects are reactive:**
1097
1366
 
1098
- ```javascript
1099
- helium({
1100
- addItem(items, item) {
1101
- items.push(item); // ❌ Won't trigger updates
1102
- }
1103
- })
1367
+ ```html
1368
+ <button @click="items.push(newItem)">Add item</button>
1369
+ <span @text="items.length"></span>
1104
1370
  ```
1105
1371
 
1106
- **✅ Pass $data and update through it:**
1372
+ Nested mutations made through `$data` are reactive too:
1107
1373
 
1108
1374
  ```javascript
1109
1375
  helium({
1110
1376
  addItem(data, item) {
1111
- data.items.push(item); // ✅ Triggers updates
1377
+ data.items.push(item); // Triggers updates
1112
1378
  }
1113
1379
  })
1114
1380
  ```
@@ -1141,58 +1407,11 @@ helium({
1141
1407
  <button @click="goodFunction($data)">Works!</button>
1142
1408
  ```
1143
1409
 
1144
- ## Security Considerations
1145
-
1146
- ### XSS Protection
1147
-
1148
- **Always sanitize user input when using @html:**
1149
-
1150
- ```html
1151
- <!-- ❌ Dangerous if userInput contains scripts -->
1152
- <div @html="userInput"></div>
1153
-
1154
- <!-- ✅ Sanitize first -->
1155
- <div @html="sanitize(userInput)"></div>
1156
- ```
1157
-
1158
- Consider using a sanitization library like [DOMPurify](https://github.com/cure53/DOMPurify):
1159
-
1160
- ```javascript
1161
- import DOMPurify from 'dompurify';
1162
-
1163
- helium({
1164
- sanitize(html) {
1165
- return DOMPurify.sanitize(html);
1166
- }
1167
- });
1168
- ```
1169
-
1170
- **Use @text for plain text:**
1171
-
1172
- ```html
1173
- <!-- ✅ Safe - automatically escapes HTML -->
1174
- <div @text="userInput"></div>
1175
- ```
1176
-
1177
- ### CSRF Protection
1178
-
1179
- Helium automatically includes CSRF tokens in same-origin requests. Add this meta tag to your HTML:
1180
-
1181
- ```html
1182
- <meta name="csrf-token" content="your-csrf-token">
1183
- ```
1184
-
1185
- All POST, PUT, PATCH, and DELETE requests will include the `X-CSRF-Token` header automatically.
1186
-
1187
- ### Content Security Policy
1188
-
1189
- If you're using a strict CSP, you may need to allow `'unsafe-eval'` since Helium uses `new Function()` to compile expressions, or use a hash/nonce for the script.
1190
-
1191
1410
  ## Error Handling
1192
1411
 
1193
1412
  ### JavaScript Expression Errors
1194
1413
 
1195
- If an expression throws an error, Helium catches it silently and continues. Check the browser console for error messages.
1414
+ If a binding expression throws at runtime, Helium logs the error and continues without updating that binding. Standard and lite retain malformed expressions as literal text for compatibility; quote intentional string constants, for example `@text="'Ready'"`.
1196
1415
 
1197
1416
  ```html
1198
1417
  <!-- If items is undefined, this won't crash the page -->
@@ -1201,13 +1420,12 @@ If an expression throws an error, Helium catches it silently and continues. Chec
1201
1420
 
1202
1421
  ### HTTP Request Errors
1203
1422
 
1204
- Failed requests log errors to the console. Handle them in your expressions:
1423
+ HTTP directives log failed requests to the console. Programmatic helpers return
1424
+ promises, so you can handle failures explicitly:
1205
1425
 
1206
1426
  ```html
1207
- <button
1208
- @post="/api/save"
1209
- @params="{ data: formData }"
1210
- @target="#message">
1427
+ <button @click="$post('/api/save', { data: formData }, '#message')
1428
+ .catch(error => saveError = error.message)">
1211
1429
  Save
1212
1430
  </button>
1213
1431
 
@@ -1216,8 +1434,54 @@ Failed requests log errors to the console. Handle them in your expressions:
1216
1434
 
1217
1435
  ### Invalid Attribute Syntax
1218
1436
 
1219
- Helium gracefully handles invalid syntax. If an expression can't be compiled, it treats it as a literal value.
1220
-
1437
+ Standard and Lite retain expressions that cannot be compiled as literal values.
1438
+ The CSP build logs the parser error and leaves the binding undefined.
1439
+
1440
+
1441
+ ## Roadmap & Known Limitations
1442
+
1443
+ Helium aims to stay tiny, so some conveniences found in larger frameworks are
1444
+ intentionally absent (or still on the roadmap). Current gaps:
1445
+
1446
+ ### Not yet supported
1447
+
1448
+ - **No DOM-removing `@if`** — only `@hidden`/`@visible`, which toggle visibility
1449
+ but keep elements in the DOM.
1450
+ - **No `$watch`/`$nextTick`, and no cross-root shared store** — each mounted
1451
+ instance has its own isolated state namespace.
1452
+
1453
+ ### Roadmap
1454
+
1455
+ Ordered by recommended implementation sequence:
1456
+
1457
+ 1. **Improve expression-compilation diagnostics** — emit a useful `console.warn`
1458
+ containing the expression and build/engine context instead of silently
1459
+ treating every compile failure as a literal value.
1460
+ 2. **Add real-browser CI** covering CSP, Turbo, SSE, focus preservation, late
1461
+ imports, generated Lite output, and package exports before adding more DOM
1462
+ lifecycle behavior.
1463
+ 3. **Document function and `this` context limitations** alongside the existing
1464
+ magic-variable guidance, with correct patterns for passing `$data`, `$el`,
1465
+ and other context explicitly.
1466
+ 4. **Add opt-in required-CSRF validation for POST/PUT/PATCH** — fail before the
1467
+ request when an application declares that a token is mandatory, without
1468
+ breaking tokenless APIs and cross-origin requests.
1469
+ 5. **Add request cancellation and explicit success/error hooks** using
1470
+ `AbortController`; define the request lifecycle before expanding loading and
1471
+ error-state APIs.
1472
+ 6. **Document a form-validation pattern first**, using native constraint
1473
+ validation and Helium state; add helpers only if examples reveal recurring
1474
+ boilerplate.
1475
+ 7. **Create a complete error-state example** covering validation, HTTP failure,
1476
+ retry, accessible messaging, and clearing stale errors.
1477
+ 8. **Complete HTTP loading-state lifecycle behavior** — `@loading` already
1478
+ exists, so define consistent success, error, cancellation, and restoration
1479
+ behavior rather than adding a second loading mechanism.
1480
+ 9. **Add debounced two-way binding updates if needed** after reproducing a real
1481
+ feedback/caret loop; event-handler debouncing already covers ordinary input
1482
+ throttling.
1483
+ 10. **Add a minimal watch/middleware system** together with a clear size budget;
1484
+ avoid adopting Alpine-scale surface area without demonstrated use cases.
1221
1485
 
1222
1486
  ## Contributing
1223
1487
 
@@ -1227,6 +1491,15 @@ Helium is open source! Contributions, issues, and feature requests are welcome.
1227
1491
  - Report issues: Create an issue on GitHub
1228
1492
  - Suggest features: Open a discussion on GitHub
1229
1493
 
1494
+ Before submitting a change, run `npm run check`. It executes the full test
1495
+ suite, creates the production bundles, and fails if any gzip size budget is
1496
+ exceeded. `npm run size` also reports raw, gzip, and Brotli sizes.
1497
+
1498
+ `helium.js` contains the shared runtime plus sections marked Standard-only.
1499
+ `npm run generate` derives `helium-lite.js`; do not edit the generated Lite file
1500
+ directly. Standard keeps keyed lists, HTTP, imports, and Turbo, while SSE remains
1501
+ an optional module.
1502
+
1230
1503
  ## License
1231
1504
 
1232
1505
  MIT License - feel free to use Helium in personal and commercial projects.