micra.js 2.3.1 → 2.3.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/CHANGELOG.md +39 -0
- package/README.md +17 -0
- package/dist/micra.cjs.js +11 -20
- package/dist/micra.cjs.js.map +2 -2
- package/dist/micra.esm.js +11 -20
- package/dist/micra.esm.js.map +2 -2
- package/dist/micra.js +11 -20
- package/dist/micra.js.map +2 -2
- package/dist/micra.min.js +2 -2
- package/llms-full.txt +16 -16
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/src/dom/each.ts +19 -2
- package/src/utils/expr.ts +3 -4
package/llms-full.txt
CHANGED
|
@@ -7,7 +7,7 @@ This file follows the llmstxt.org "expanded" convention: it inlines code recipes
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install micra.js@^2.3.
|
|
10
|
+
npm install micra.js@^2.3.2
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
```ts
|
|
@@ -17,7 +17,7 @@ import * as Micra from 'micra.js'
|
|
|
17
17
|
Or CDN (no build step):
|
|
18
18
|
|
|
19
19
|
```html
|
|
20
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
20
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
This exposes a global `Micra` object.
|
|
@@ -146,7 +146,7 @@ Everything else (`window`, `document`, `fetch`, `eval`, `constructor`, `setTimeo
|
|
|
146
146
|
<button @click="inc">+</button>
|
|
147
147
|
</div>
|
|
148
148
|
|
|
149
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
149
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
150
150
|
<script>
|
|
151
151
|
Micra.define('counter', {
|
|
152
152
|
state: { count: 0 },
|
|
@@ -190,7 +190,7 @@ Everything else (`window`, `document`, `fetch`, `eval`, `constructor`, `setTimeo
|
|
|
190
190
|
</footer>
|
|
191
191
|
</div>
|
|
192
192
|
|
|
193
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
193
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
194
194
|
<script>
|
|
195
195
|
Micra.define('todo-app', {
|
|
196
196
|
state: {
|
|
@@ -262,7 +262,7 @@ Everything else (`window`, `document`, `fetch`, `eval`, `constructor`, `setTimeo
|
|
|
262
262
|
<p data-if="filtered().length === 0">No matches.</p>
|
|
263
263
|
</div>
|
|
264
264
|
|
|
265
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
265
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
266
266
|
<script>
|
|
267
267
|
Micra.define('users-table', {
|
|
268
268
|
state: {
|
|
@@ -303,7 +303,7 @@ Everything else (`window`, `document`, `fetch`, `eval`, `constructor`, `setTimeo
|
|
|
303
303
|
<p data-if="success">Invitation sent ✓</p>
|
|
304
304
|
</form>
|
|
305
305
|
|
|
306
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
306
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
307
307
|
<script>
|
|
308
308
|
Micra.define('invite-form', {
|
|
309
309
|
state: { email: '', loading: false, error: '', success: false },
|
|
@@ -345,7 +345,7 @@ Everything else (`window`, `document`, `fetch`, `eval`, `constructor`, `setTimeo
|
|
|
345
345
|
</div>
|
|
346
346
|
</div>
|
|
347
347
|
|
|
348
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
348
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
349
349
|
<script>
|
|
350
350
|
Micra.define('open-modal-btn', {
|
|
351
351
|
open() {
|
|
@@ -394,7 +394,7 @@ Everything else (`window`, `document`, `fetch`, `eval`, `constructor`, `setTimeo
|
|
|
394
394
|
<section data-if="tab === 'security'">Security content</section>
|
|
395
395
|
</div>
|
|
396
396
|
|
|
397
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
397
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
398
398
|
<script>
|
|
399
399
|
Micra.define('tabs', {
|
|
400
400
|
state: { tab: 'overview' },
|
|
@@ -414,7 +414,7 @@ Everything else (`window`, `document`, `fetch`, `eval`, `constructor`, `setTimeo
|
|
|
414
414
|
<button @click="upgrade" data-if="plan !== 'enterprise'">Upgrade</button>
|
|
415
415
|
</div>
|
|
416
416
|
|
|
417
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
417
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
418
418
|
<script>
|
|
419
419
|
Micra.define('user-card', {
|
|
420
420
|
state: { name: '', plan: '' },
|
|
@@ -445,7 +445,7 @@ Everything else (`window`, `document`, `fetch`, `eval`, `constructor`, `setTimeo
|
|
|
445
445
|
<p data-if="!loading && results.length === 0 && query">No results.</p>
|
|
446
446
|
</div>
|
|
447
447
|
|
|
448
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
448
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
449
449
|
<script>
|
|
450
450
|
Micra.define('search', {
|
|
451
451
|
state: { query: '', results: [], loading: false },
|
|
@@ -479,7 +479,7 @@ Everything else (`window`, `document`, `fetch`, `eval`, `constructor`, `setTimeo
|
|
|
479
479
|
<p data-if="loading">Loading chart…</p>
|
|
480
480
|
</div>
|
|
481
481
|
|
|
482
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
482
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
483
483
|
<script>
|
|
484
484
|
Micra.define('revenue-chart', {
|
|
485
485
|
state: { loading: true },
|
|
@@ -511,7 +511,7 @@ Everything else (`window`, `document`, `fetch`, `eval`, `constructor`, `setTimeo
|
|
|
511
511
|
<p data-if="!loading && rows.length === 0">No results.</p>
|
|
512
512
|
</div>
|
|
513
513
|
|
|
514
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
514
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
515
515
|
<script>
|
|
516
516
|
Micra.define('search-bar', {
|
|
517
517
|
state: { query: '' },
|
|
@@ -542,7 +542,7 @@ the same page. Twelve lines of glue, written once.
|
|
|
542
542
|
<main hx-get="/page/home" hx-trigger="load" hx-swap="innerHTML"></main>
|
|
543
543
|
|
|
544
544
|
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2/dist/htmx.min.js"></script>
|
|
545
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
545
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
546
546
|
<script>
|
|
547
547
|
Micra.define('counter', {
|
|
548
548
|
state: { count: 0 },
|
|
@@ -596,7 +596,7 @@ a `turbo:load` mirror so the second navigation doesn't ghost.
|
|
|
596
596
|
```ruby
|
|
597
597
|
# config/importmap.rb
|
|
598
598
|
pin "micra",
|
|
599
|
-
to: "https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
599
|
+
to: "https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.esm.js",
|
|
600
600
|
preload: true
|
|
601
601
|
```
|
|
602
602
|
|
|
@@ -717,9 +717,9 @@ import { ref, computed } from 'vue'
|
|
|
717
717
|
import Alpine from 'alpinejs'
|
|
718
718
|
|
|
719
719
|
// ❌ unpkg CDN — blocked by Claude artifacts and most AI sandbox CSPs
|
|
720
|
-
<script src="https://unpkg.com/micra.js@2.3.
|
|
720
|
+
<script src="https://unpkg.com/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
721
721
|
// ✅ Use jsDelivr instead — it auto-mirrors npm and is CSP-allowlisted everywhere
|
|
722
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
722
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
723
723
|
```
|
|
724
724
|
|
|
725
725
|
# Final checklist
|
package/llms.txt
CHANGED
|
@@ -25,7 +25,7 @@ import * as Micra from 'micra.js'
|
|
|
25
25
|
Or via CDN (no build step):
|
|
26
26
|
|
|
27
27
|
```html
|
|
28
|
-
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.
|
|
28
|
+
<script src="https://cdn.jsdelivr.net/npm/micra.js@2.3.2/dist/micra.min.js"></script>
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
This exposes a global `Micra` object.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "micra.js",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "Lightweight reactive UI framework for server-rendered pages — reactive state, directives, event bus. < 5 KB gzip.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/micra.cjs.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"test": "vitest run",
|
|
31
31
|
"test:watch": "vitest",
|
|
32
32
|
"test:coverage": "vitest run --coverage",
|
|
33
|
-
"docs:sync": "rm -rf site/dist && mkdir -p site/dist && cp -R dist/* site/dist/",
|
|
33
|
+
"docs:sync": "rm -rf site/dist && mkdir -p site/dist && cp -R dist/* site/dist/ && cp llms.txt llms-full.txt site/",
|
|
34
34
|
"docs:build": "npm run build && npm run docs:sync",
|
|
35
35
|
"docs:dev": "npm run docs:sync && npx serve -p 4321 site"
|
|
36
36
|
},
|
package/src/dom/each.ts
CHANGED
|
@@ -106,8 +106,25 @@ function createRowNode<S extends StateRecord>(
|
|
|
106
106
|
): MicraElement {
|
|
107
107
|
const frag = tmpl.content.cloneNode(true) as DocumentFragment
|
|
108
108
|
let node: MicraElement
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
// Single-root detection must ignore whitespace-only text nodes — a
|
|
110
|
+
// pretty-printed `<template>\n <tr>…</tr>\n</template>` is still one root.
|
|
111
|
+
// Wrapping a lone <tr> in <micra-each-item> would put invalid content
|
|
112
|
+
// inside <tbody> and break `tbody > tr` selectors. Only TOP-LEVEL child
|
|
113
|
+
// nodes are scanned (O(1-ish), not O(subtree)); NBSP counts as meaningful
|
|
114
|
+
// (it renders), so it keeps the wrapper. Comments beside the root are
|
|
115
|
+
// dropped — they don't render and aren't worth a wrapper in <tbody>.
|
|
116
|
+
const first = frag.firstElementChild as MicraElement | null
|
|
117
|
+
// meaningful = any char with code > 32 (NBSP included; \t \n \f \r and
|
|
118
|
+
// space excluded) in a top-level text node
|
|
119
|
+
const single =
|
|
120
|
+
!!first &&
|
|
121
|
+
!first.nextElementSibling &&
|
|
122
|
+
!Array.prototype.some.call(
|
|
123
|
+
frag.childNodes,
|
|
124
|
+
(c: Node) => c.nodeType === 3 && /[^\x00- ]/.test(c.textContent!),
|
|
125
|
+
)
|
|
126
|
+
if (single) {
|
|
127
|
+
node = first!
|
|
111
128
|
} else {
|
|
112
129
|
node = document.createElement('micra-each-item') as MicraElement
|
|
113
130
|
node.style.display = 'contents'
|
package/src/utils/expr.ts
CHANGED
|
@@ -52,10 +52,9 @@ const SIMPLE_PATH = /^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/
|
|
|
52
52
|
* Globals reachable from directive expressions. Anything else (window, fetch,
|
|
53
53
|
* constructor, eval, ...) is shadowed by SAFE_OUTER and resolves to undefined.
|
|
54
54
|
*/
|
|
55
|
-
const ALLOWED_GLOBALS = new Set<string>(
|
|
56
|
-
'Math
|
|
57
|
-
|
|
58
|
-
])
|
|
55
|
+
const ALLOWED_GLOBALS = new Set<string>(
|
|
56
|
+
'Math,JSON,Date,String,Number,Boolean,Array,Object,parseInt,parseFloat,isNaN,isFinite,NaN,Infinity,undefined'.split(','),
|
|
57
|
+
)
|
|
59
58
|
|
|
60
59
|
/**
|
|
61
60
|
* Outer `with()` scope. Its `has` trap claims every non-whitelisted identifier
|