micra.js 1.1.0 → 2.1.0

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
@@ -1,6 +1,6 @@
1
1
  # Micra.js
2
2
 
3
- Micra.js is a lightweight reactive TypeScript framework for small sites and SaaS apps. It gives you reactive state, DOM directives, keyed list rendering, an event bus, SSR-friendly props, and auto-mounting in about 3.7 KB gzip.
3
+ Micra.js is a lightweight reactive TypeScript framework for small sites and SaaS apps. It gives you reactive state, DOM directives, keyed list rendering, an event bus, SSR-friendly props, and auto-mounting in about 5 KB gzip.
4
4
 
5
5
  ## What is Micra.js?
6
6
 
@@ -27,15 +27,19 @@ Use it when you want:
27
27
  <button @click="increment">+</button>
28
28
  </div>
29
29
 
30
- <script src="https://unpkg.com/micra.js/dist/micra.min.js"></script>
30
+ <script src="https://cdn.jsdelivr.net/npm/micra.js/dist/micra.min.js"></script>
31
31
  <script>
32
- Micra.define('counter', {
32
+ Micra.define("counter", {
33
33
  state: { count: 0 },
34
- increment() { this.state.count++ },
35
- decrement() { this.state.count-- },
36
- })
37
-
38
- Micra.start()
34
+ increment() {
35
+ this.state.count++;
36
+ },
37
+ decrement() {
38
+ this.state.count--;
39
+ },
40
+ });
41
+
42
+ Micra.start();
39
43
  </script>
40
44
  ```
41
45
 
@@ -44,7 +48,7 @@ Use it when you want:
44
48
  ### CDN
45
49
 
46
50
  ```html
47
- <script src="https://unpkg.com/micra.js/dist/micra.min.js"></script>
51
+ <script src="https://cdn.jsdelivr.net/npm/micra.js/dist/micra.min.js"></script>
48
52
  ```
49
53
 
50
54
  ### npm
@@ -54,7 +58,7 @@ npm install micra.js
54
58
  ```
55
59
 
56
60
  ```ts
57
- import * as Micra from 'micra.js'
61
+ import * as Micra from "micra.js";
58
62
  ```
59
63
 
60
64
  ## Basic usage
@@ -71,42 +75,42 @@ A counter mounted automatically from `data-component`:
71
75
  ```
72
76
 
73
77
  ```ts
74
- import * as Micra from 'micra.js'
78
+ import * as Micra from "micra.js";
75
79
 
76
- Micra.define('counter', {
80
+ Micra.define("counter", {
77
81
  state: { count: 0 },
78
82
 
79
83
  increment() {
80
- this.state.count++
84
+ this.state.count++;
81
85
  },
82
86
 
83
87
  decrement() {
84
- this.state.count--
88
+ this.state.count--;
85
89
  },
86
90
 
87
91
  reset() {
88
- this.state.count = 0
92
+ this.state.count = 0;
89
93
  },
90
- })
94
+ });
91
95
 
92
- Micra.start()
96
+ Micra.start();
93
97
  ```
94
98
 
95
99
  ## Directives
96
100
 
97
- | Directive | Example | Description |
98
- |---|---|---|
99
- | `data-text` | `data-text="name"` | Set `textContent` |
100
- | `data-html` | `data-html="content"` | Set `innerHTML` |
101
- | `data-if` | `data-if="count > 0"` | Toggle display |
102
- | `data-show` | `data-show="loaded"` | Alias of `data-if` |
103
- | `data-bind` | `data-bind="href:url, disabled:loading"` | Bind attributes |
104
- | `data-model` | `data-model="search"` | Two-way input binding |
105
- | `data-each` | `data-each="items" data-key="id"` | List rendering |
106
- | `data-ref` | `data-ref="chart"` | DOM ref in `this.refs` |
107
- | `data-class` | `data-class="active:isActive"` | Toggle classes additively |
108
- | `data-on` | `data-on="click:save"` | Bind DOM events |
109
- | `@event` | `@click="increment"` | Shorthand event binding |
101
+ | Directive | Example | Description |
102
+ | ------------ | ---------------------------------------- | ------------------------- |
103
+ | `data-text` | `data-text="name"` | Set `textContent` |
104
+ | `data-html` | `data-html="content"` | Set `innerHTML` |
105
+ | `data-if` | `data-if="count > 0"` | Toggle display |
106
+ | `data-show` | `data-show="loaded"` | Alias of `data-if` |
107
+ | `data-bind` | `data-bind="href:url, disabled:loading"` | Bind attributes |
108
+ | `data-model` | `data-model="search"` | Two-way input binding |
109
+ | `data-each` | `data-each="items" data-key="id"` | List rendering |
110
+ | `data-ref` | `data-ref="chart"` | DOM ref in `this.refs` |
111
+ | `data-class` | `data-class="active:isActive"` | Toggle classes additively |
112
+ | `data-on` | `data-on="click:save"` | Bind DOM events |
113
+ | `@event` | `@click="increment"` | Shorthand event binding |
110
114
 
111
115
  ## API reference summary
112
116
 
@@ -164,3 +168,6 @@ this.on(event, handler)
164
168
  - [SSR](./docs/ssr.md)
165
169
  - [Examples](./docs/examples.md)
166
170
  - [API reference](./docs/api-reference.md)
171
+ - Recipes:
172
+ - [Todo app](./docs/recipes/todo-app.md)
173
+ - [Server-sent events (SSE)](./docs/recipes/sse.md)
@@ -21,3 +21,9 @@ export declare function queryAll(root: ParentNode, sel: string): Element[];
21
21
  * owned by that nested component, not by root's component — so we skip it.
22
22
  */
23
23
  export declare function queryOwn(root: Element, attr: string): Element[];
24
+ /**
25
+ * Like queryOwn but accepts an arbitrary CSS selector. Used by bindAtEvents
26
+ * which scans `*` for `@`-prefixed attribute names (no attribute selector exists
27
+ * for those).
28
+ */
29
+ export declare function queryOwnAll(root: Element, sel: string): Element[];
package/dist/index.d.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  * - SSR-friendly: Micra.start() is safe to call multiple times
18
18
  * - Directive cache: O(1) re-renders after first mount
19
19
  *
20
- * Size target: < 5 KB minified+gzipped
20
+ * Size target: < 5.5 KB minified+gzipped
21
21
  *
22
22
  * @module Micra
23
23
  */
package/dist/micra.cjs.js CHANGED
@@ -1,4 +1,4 @@
1
- /* Micra.js v1.1.0 — https://github.com/micra-js/micra — MIT */
1
+ /* Micra.js v2.1.0 — https://github.com/micra-js/micra — MIT */
2
2
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -62,17 +62,19 @@ async function micraFetch(url, options = {}) {
62
62
  if (method === "GET" || method === "HEAD") {
63
63
  const params = {};
64
64
  for (const [k, v] of Object.entries(options)) {
65
- if (k !== "method" && k !== "headers" && v != null) params[k] = String(v);
65
+ if (k !== "method" && k !== "headers" && k !== "signal" && v != null)
66
+ params[k] = String(v);
66
67
  }
67
68
  if (Object.keys(params).length)
68
69
  finalUrl += (url.includes("?") ? "&" : "?") + new URLSearchParams(params);
69
- } else {
70
+ } else if (options.body !== void 0) {
70
71
  headers["Content-Type"] = "application/json";
71
- body = JSON.stringify(options.body !== void 0 ? options.body : options);
72
+ body = JSON.stringify(options.body);
72
73
  }
73
74
  const res = await fetch(finalUrl, {
74
75
  method,
75
76
  headers,
77
+ ...options.signal !== void 0 ? { signal: options.signal } : {},
76
78
  ...body !== void 0 ? { body } : {}
77
79
  });
78
80
  if (!res.ok)
@@ -259,7 +261,13 @@ function queryAll(root, sel) {
259
261
  return Array.from(root.querySelectorAll(sel));
260
262
  }
261
263
  function queryOwn(root, attr) {
262
- return queryAll(root, `[${attr}]`).filter((el) => {
264
+ return filterOwn(root, queryAll(root, `[${attr}]`));
265
+ }
266
+ function queryOwnAll(root, sel) {
267
+ return filterOwn(root, queryAll(root, sel));
268
+ }
269
+ function filterOwn(root, els) {
270
+ return els.filter((el) => {
263
271
  let node = el.parentElement;
264
272
  while (node && node !== root) {
265
273
  if (node.hasAttribute("data-component")) return false;
@@ -279,7 +287,21 @@ function applyHtml(el, expr, state) {
279
287
  var _a;
280
288
  el.innerHTML = String((_a = evalExpr(expr, state)) != null ? _a : "");
281
289
  }
282
- function applyIf(el, expr, state) {
290
+ function applyIf(binding, state) {
291
+ const el = binding.el;
292
+ const truthy = !!evalExpr(binding.expr, state);
293
+ if (truthy) {
294
+ const ph = binding.placeholder;
295
+ if (ph && ph.parentNode) ph.parentNode.replaceChild(el, ph);
296
+ } else {
297
+ const parent = el.parentNode;
298
+ if (parent) {
299
+ if (!binding.placeholder) binding.placeholder = document.createComment("if");
300
+ parent.replaceChild(binding.placeholder, el);
301
+ }
302
+ }
303
+ }
304
+ function applyShow(el, expr, state) {
283
305
  el.style.display = evalExpr(expr, state) ? "" : "none";
284
306
  }
285
307
  function applyBind(el, pairs, state) {
@@ -354,10 +376,10 @@ function applyDirectives(root, state, rawState, _instance) {
354
376
  applyFromList(el.__micraCache, state, rawState);
355
377
  }
356
378
  function applyFromList(cache, state, rawState) {
379
+ cache.if.forEach((b) => applyIf(b, state));
357
380
  cache.text.forEach((b) => applyText(b.el, b.expr, state));
358
381
  cache.html.forEach((b) => applyHtml(b.el, b.expr, state));
359
- cache.if.forEach((b) => applyIf(b.el, b.expr, state));
360
- cache.show.forEach((b) => applyIf(b.el, b.expr, state));
382
+ cache.show.forEach((b) => applyShow(b.el, b.expr, state));
361
383
  cache.bind.forEach((b) => applyBind(b.el, b.pairs, state));
362
384
  cache.model.forEach((b) => applyModel(b.el, b.expr.trim(), rawState));
363
385
  cache.class.forEach((b) => applyClass(b.el, b.pairs, state));
@@ -432,7 +454,7 @@ function bindDataOn(root, instance) {
432
454
  }
433
455
  function bindAtEvents(root, instance) {
434
456
  const isFragment = root.nodeType === 11;
435
- const all = isFragment ? queryAll(root, "*") : queryAll(root, "*");
457
+ const all = isFragment ? queryAll(root, "*") : queryOwnAll(root, "*");
436
458
  if (!isFragment && !all.includes(root)) all.unshift(root);
437
459
  for (const el of all) {
438
460
  const mEl = el;
@@ -503,6 +525,7 @@ function renderList(root, state, rawState, instance) {
503
525
  const marker = tmpl.__micraMarker;
504
526
  const keyMap = tmpl.__micraNodes;
505
527
  const parent = marker.parentNode;
528
+ if (!parent) return;
506
529
  if (!Array.isArray(items)) {
507
530
  tmpl.__micraList.forEach((n) => n.remove());
508
531
  tmpl.__micraList = [];