micra.js 2.2.1 → 2.3.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.
- package/CHANGELOG.md +118 -0
- package/README.md +44 -0
- package/dist/core/bus.d.ts +6 -4
- package/dist/core/reactive.d.ts +4 -0
- package/dist/dom/directives.d.ts +2 -2
- package/dist/dom/each.d.ts +5 -2
- package/dist/dom/scan.d.ts +5 -9
- package/dist/index.d.ts +1 -1
- package/dist/micra.cjs.js +75 -62
- package/dist/micra.cjs.js.map +3 -3
- package/dist/micra.esm.js +75 -62
- package/dist/micra.esm.js.map +3 -3
- package/dist/micra.js +75 -62
- package/dist/micra.js.map +3 -3
- package/dist/micra.min.js +2 -2
- package/dist/types.d.ts +46 -6
- package/llms-full.txt +162 -14
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/src/core/bus.ts +15 -6
- package/src/core/mount.ts +6 -5
- package/src/core/reactive.ts +6 -1
- package/src/dom/directives.ts +1 -3
- package/src/dom/each.ts +97 -47
- package/src/dom/scan.ts +5 -25
- package/src/index.ts +3 -0
- package/src/types.ts +57 -6
- package/src/dom/query.ts +0 -50
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,124 @@ All notable changes to Micra.js will be documented in this file. Format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versioning follows
|
|
5
5
|
[SemVer](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.3.1] — 2026-05-30
|
|
8
|
+
|
|
9
|
+
### Performance
|
|
10
|
+
|
|
11
|
+
- **Batch scheduler now uses `queueMicrotask` instead of
|
|
12
|
+
`Promise.resolve().then(...)`.** Each render batch enqueues a single
|
|
13
|
+
microtask instead of allocating a Promise plus a reaction job, and the
|
|
14
|
+
flush callback is hoisted out of the hot path so it isn't re-created on
|
|
15
|
+
every `schedule()` call. Behaviour is identical — same microtask timing,
|
|
16
|
+
same write-collapsing. No public-API change.
|
|
17
|
+
|
|
18
|
+
### Internal — dead-code removal
|
|
19
|
+
|
|
20
|
+
- Removed the `src/dom/query.ts` module (`queryAll` / `queryOwn` /
|
|
21
|
+
`queryOwnAll` / `filterOwn`). It had no importers since the 2.2.0
|
|
22
|
+
single-pass scan replaced per-render `querySelectorAll` calls with one
|
|
23
|
+
`TreeWalker` traversal — esbuild already tree-shook it out of the
|
|
24
|
+
bundle, so this is a source-only cleanup.
|
|
25
|
+
- Removed two dead bookkeeping writes: `node.__micraEach` and
|
|
26
|
+
`node.__micraKey` were assigned during list rendering but never read
|
|
27
|
+
(keys live in the keyed-diff `Map`; the no-key path doesn't tag rows).
|
|
28
|
+
Dropped the matching fields from `MicraElement`.
|
|
29
|
+
- Dropped the unused `instance` parameter from `applyDirectives` — it was
|
|
30
|
+
never referenced in the body.
|
|
31
|
+
|
|
32
|
+
### Docs
|
|
33
|
+
|
|
34
|
+
- New [Rails + Micra recipe](https://github.com/denisfl/micra.js/blob/master/docs/recipes/rails.md)
|
|
35
|
+
(`docs/recipes/rails.md` + a site page): manual importmap integration,
|
|
36
|
+
the `micra-rails` gem with its caveats, a Tasks board demonstrating SSR
|
|
37
|
+
props / CSRF-attached `this.fetch` / cross-component bus, and the Turbo
|
|
38
|
+
Drive / Streams / Frames mount-and-cleanup story.
|
|
39
|
+
- README gains a **TypeScript** section spelling out what's checked
|
|
40
|
+
end-to-end (state, methods, event payloads) versus what isn't (the
|
|
41
|
+
expression strings inside `data-*` attributes).
|
|
42
|
+
- Landing page gains **Speed** (cross-library benchmark cards) and **AI
|
|
43
|
+
sandboxes** (copy-the-LLM-prompt) sections.
|
|
44
|
+
|
|
45
|
+
### Bundle
|
|
46
|
+
|
|
47
|
+
- **5.5 KB gzip** (5582 bytes) — a few bytes lighter than 2.3.0 after the
|
|
48
|
+
dead-code removal.
|
|
49
|
+
|
|
50
|
+
## [2.3.0] — 2026-05-30
|
|
51
|
+
|
|
52
|
+
### TypeScript — type-safe event bus
|
|
53
|
+
|
|
54
|
+
- **New augmentable `MicraEvents` interface.** Declare your app's events
|
|
55
|
+
once and `Micra.emit` / `Micra.on` / `this.emit` / `this.on` enforce
|
|
56
|
+
payload types and arity at the call site:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
declare module 'micra.js' {
|
|
60
|
+
interface MicraEvents {
|
|
61
|
+
'cart:updated': { count: number }
|
|
62
|
+
'modal:close': void
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Micra.emit('cart:updated', { count: 3 }) // ✓
|
|
67
|
+
Micra.emit('cart:updated', { count: '3' }) // ✗ type error
|
|
68
|
+
Micra.emit('modal:close') // ✓ void → no args
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- Events that are NOT declared in `MicraEvents` keep the previous
|
|
72
|
+
behaviour — payload typed as `unknown`, optional argument. Untyped
|
|
73
|
+
code keeps compiling unchanged.
|
|
74
|
+
- New exported types: `MicraEvents`, `EventPayload<K>`, `EmitArgs<K>`.
|
|
75
|
+
- Bundle stays at **5.4 KB gzip** — types only, no runtime change.
|
|
76
|
+
|
|
77
|
+
### Breaking — types only
|
|
78
|
+
|
|
79
|
+
- The legacy `on<T>(event, handler)` generic now infers `T` as the
|
|
80
|
+
event *key*, not the handler payload. Code that explicitly passed a
|
|
81
|
+
payload type via the generic (`Micra.on<User>('user:updated', h)`)
|
|
82
|
+
still compiles, but `h`'s parameter falls back to `unknown` unless
|
|
83
|
+
the event is declared in `MicraEvents`. Migration: register the
|
|
84
|
+
event via `declare module 'micra.js'` and drop the explicit generic.
|
|
85
|
+
No runtime impact.
|
|
86
|
+
|
|
87
|
+
### Performance — non-keyed `data-each` now reuses DOM nodes
|
|
88
|
+
|
|
89
|
+
- **Non-keyed `<template data-each>` no longer re-renders the whole list
|
|
90
|
+
on every update.** The new path keeps the first `min(prev, next)` row
|
|
91
|
+
nodes in place — only the length delta is touched (tail removed when
|
|
92
|
+
the list shrinks, new rows cloned when it grows). Each retained row
|
|
93
|
+
gets a fresh `itemState` and a re-applied directive pass through its
|
|
94
|
+
cached `__micraScan`, so content updates correctly without the
|
|
95
|
+
remove/re-clone overhead.
|
|
96
|
+
- Row identity is now stable across renders for the no-key path: event
|
|
97
|
+
listeners bound via `data-on` / `@event` / `data-model` survive
|
|
98
|
+
re-renders without re-binding, and DOM-level state (focus, scroll,
|
|
99
|
+
CSS transitions) is preserved on retained rows.
|
|
100
|
+
- Items that didn't change (same reference + same index) skip
|
|
101
|
+
`applyDirectives` entirely when only the `data-each` source array is
|
|
102
|
+
the trigger for this render cycle — same `canSkipUnchanged`
|
|
103
|
+
optimisation the keyed path already had.
|
|
104
|
+
- Bundle: **5.5 KB gzip** (raised guard from 5.4 → 5.5 to give the
|
|
105
|
+
shared row-creation helper room; net code is slightly smaller after
|
|
106
|
+
factoring `createRowNode` out of both keyed and non-keyed paths).
|
|
107
|
+
|
|
108
|
+
### Breaking — non-keyed multi-root rows now wrap in `<micra-each-item>`
|
|
109
|
+
|
|
110
|
+
- Templates whose `data-each` content has more than one top-level node
|
|
111
|
+
now render each row inside a `<micra-each-item style="display:contents">`
|
|
112
|
+
wrapper, mirroring the keyed path's existing behaviour. The wrapper is
|
|
113
|
+
visually inert (CSS `display:contents` opts out of the box model) but
|
|
114
|
+
it does add one node to the parse tree.
|
|
115
|
+
- Impact:
|
|
116
|
+
- **CSS:** child selectors that targeted `parent > .row` will now
|
|
117
|
+
match `parent > micra-each-item` instead. Use descendant selectors
|
|
118
|
+
(`parent .row`) or update the rules.
|
|
119
|
+
- **Invalid HTML contexts:** templates whose rows are `<tr>` / `<td>` /
|
|
120
|
+
`<li>` inside `<tbody>` / `<tr>` / `<ul>` cannot legally have a
|
|
121
|
+
wrapper between the parent and the row. Hoist the wrapper into the
|
|
122
|
+
template (so the row is single-rooted) or use a `data-key`.
|
|
123
|
+
- Single-root templates are unchanged — by far the common case.
|
|
124
|
+
|
|
7
125
|
## [2.2.1] — 2026-05-28
|
|
8
126
|
|
|
9
127
|
### Performance — batched first list render
|
package/README.md
CHANGED
|
@@ -71,6 +71,48 @@ npm install micra.js
|
|
|
71
71
|
import * as Micra from "micra.js";
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
### TypeScript
|
|
75
|
+
|
|
76
|
+
The npm package ships its own `dist/index.d.ts` — no `@types/micra.js` package
|
|
77
|
+
needed. Inside every method body and lifecycle hook, both `this.state.X` and
|
|
78
|
+
`this.someMethod()` are fully checked at the call site (both `state` and the
|
|
79
|
+
method set are inferred from the literal you pass to `Micra.define`).
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import * as Micra from "micra.js";
|
|
83
|
+
|
|
84
|
+
Micra.define("counter", {
|
|
85
|
+
state: { count: 0 },
|
|
86
|
+
inc() {
|
|
87
|
+
this.state.count++; // ✓ number
|
|
88
|
+
this.dec(); // ✓ inferred sibling method
|
|
89
|
+
// this.foo(); // ✗ Property 'foo' does not exist
|
|
90
|
+
},
|
|
91
|
+
dec() { this.state.count--; },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Type-safe event bus via declaration merging
|
|
95
|
+
declare module "micra.js" {
|
|
96
|
+
interface MicraEvents {
|
|
97
|
+
"cart:updated": { count: number };
|
|
98
|
+
"modal:close": void;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Micra.emit("cart:updated", { count: 3 }); // ✓
|
|
103
|
+
Micra.emit("cart:updated", { count: "3" }); // ✗ type error
|
|
104
|
+
Micra.emit("modal:close"); // ✓ void → no args
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**What's checked:** imports, state shape, method names, event-bus payloads,
|
|
108
|
+
lifecycle hooks, refs, `Micra.mount()` return type.
|
|
109
|
+
|
|
110
|
+
**What's not:** the expression strings inside `data-text="…"` / `@click="…"`
|
|
111
|
+
attributes — those are plain HTML to the IDE and validated only at mount
|
|
112
|
+
time. Same trade-off as Alpine.js `x-*` and petite-vue `v-*`; the
|
|
113
|
+
alternatives are JSX or a single-file-component compiler, neither of which
|
|
114
|
+
Micra ships.
|
|
115
|
+
|
|
74
116
|
## Basic usage
|
|
75
117
|
|
|
76
118
|
A counter mounted automatically from `data-component`:
|
|
@@ -182,6 +224,8 @@ this.on(event, handler)
|
|
|
182
224
|
- Recipes:
|
|
183
225
|
- [Todo app](./docs/recipes/todo-app.md)
|
|
184
226
|
- [Server-sent events (SSE)](./docs/recipes/sse.md)
|
|
227
|
+
- [htmx bridge](./docs/recipes/htmx.md)
|
|
228
|
+
- [Rails + Micra](./docs/recipes/rails.md)
|
|
185
229
|
|
|
186
230
|
## Code generation with LLMs
|
|
187
231
|
|
package/dist/core/bus.d.ts
CHANGED
|
@@ -10,23 +10,25 @@
|
|
|
10
10
|
* Component instances subscribe via `instance.on()` which auto-registers
|
|
11
11
|
* the unsub token in `instance.__micraSubs` for cleanup on destroy().
|
|
12
12
|
*/
|
|
13
|
-
import type {
|
|
13
|
+
import type { EmitArgs, EventPayload, UnsubFn } from '../types';
|
|
14
14
|
/**
|
|
15
15
|
* Subscribe to a named event. Returns an unsubscribe function.
|
|
16
|
+
* Payload is typed via the `MicraEvents` interface (augmentable).
|
|
16
17
|
*
|
|
17
18
|
* @example
|
|
18
19
|
* const unsub = on('user:login', (user) => console.log(user))
|
|
19
20
|
* unsub() // stop listening
|
|
20
21
|
*/
|
|
21
|
-
export declare function on<
|
|
22
|
+
export declare function on<K extends string>(event: K, handler: (payload: EventPayload<K>) => void): UnsubFn;
|
|
22
23
|
/**
|
|
23
24
|
* Unsubscribe a specific handler from an event.
|
|
24
25
|
*/
|
|
25
|
-
export declare function off(event:
|
|
26
|
+
export declare function off<K extends string>(event: K, handler: (payload: EventPayload<K>) => void): void;
|
|
26
27
|
/**
|
|
27
28
|
* Publish an event to all subscribers. Errors are caught per-handler.
|
|
29
|
+
* Payload is typed via the `MicraEvents` interface (augmentable).
|
|
28
30
|
*
|
|
29
31
|
* @example
|
|
30
32
|
* emit('user:updated', { id: 1, name: 'Alice' })
|
|
31
33
|
*/
|
|
32
|
-
export declare function emit(event:
|
|
34
|
+
export declare function emit<K extends string>(event: K, ...args: EmitArgs<K>): void;
|
package/dist/core/reactive.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export declare function createReactiveState<S extends StateRecord>(obj: S, sched
|
|
|
23
23
|
* Return a debounce function that defers `render` to the next microtask.
|
|
24
24
|
* Multiple calls within the same tick collapse to a single render.
|
|
25
25
|
*
|
|
26
|
+
* Uses `queueMicrotask` so each batch enqueues a single microtask instead of
|
|
27
|
+
* allocating a Promise + reaction job. `flush` is hoisted out of the hot path
|
|
28
|
+
* so it isn't re-created on every schedule() call.
|
|
29
|
+
*
|
|
26
30
|
* @example
|
|
27
31
|
* const schedule = createScheduler(render)
|
|
28
32
|
* schedule() // defers render
|
package/dist/dom/directives.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Important: this module does NOT handle data-each — see dom/each.ts.
|
|
13
13
|
*/
|
|
14
|
-
import type {
|
|
14
|
+
import type { ScanIndex, StateRecord } from '../types';
|
|
15
15
|
import { warn } from '../utils/expr';
|
|
16
16
|
/**
|
|
17
17
|
* Apply all non-each directives to a component subtree.
|
|
@@ -23,7 +23,7 @@ import { warn } from '../utils/expr';
|
|
|
23
23
|
* @param state - Expression state (may include item/index for each rows)
|
|
24
24
|
* @param rawState - Raw (non-proxy) state for model sync
|
|
25
25
|
*/
|
|
26
|
-
export declare function applyDirectives
|
|
26
|
+
export declare function applyDirectives(scan: ScanIndex, state: StateRecord, rawState: StateRecord): void;
|
|
27
27
|
/**
|
|
28
28
|
* Validate directive usage and emit dev warnings.
|
|
29
29
|
* Called once after the initial render of a component, with the already-built
|
package/dist/dom/each.d.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Responsibilities:
|
|
5
5
|
* - Process `<template data-each="items" data-key="id">` elements
|
|
6
6
|
* - Keyed diff: reuse/reorder DOM nodes by key — O(n) with a Map
|
|
7
|
-
* - Non-keyed fallback:
|
|
7
|
+
* - Non-keyed fallback: length-based positional reuse — min(old, new) rows
|
|
8
|
+
* are kept as-is, the tail is removed or new rows are appended
|
|
8
9
|
* - Apply directives to each row with a scoped itemState
|
|
9
10
|
*
|
|
10
11
|
* LLM NOTE: renderList() is called on every render cycle AFTER applyDirectives().
|
|
@@ -12,7 +13,9 @@
|
|
|
12
13
|
* Each row node gets its own ScanIndex cached on `node.__micraScan` so
|
|
13
14
|
* re-renders of that row don't re-walk the DOM.
|
|
14
15
|
* Keyed mode (data-key present) mutates the DOM in-place — nodes are
|
|
15
|
-
* created once and reused. Non-keyed mode
|
|
16
|
+
* created once and reused. Non-keyed mode also reuses existing nodes
|
|
17
|
+
* positionally: only the length delta is touched, the rest gets a fresh
|
|
18
|
+
* itemState and re-applies directives.
|
|
16
19
|
*/
|
|
17
20
|
import type { InternalInstance, StateRecord } from '../types';
|
|
18
21
|
/**
|
package/dist/dom/scan.d.ts
CHANGED
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
* traversal that classifies every directive attribute in a single visit.
|
|
6
6
|
*
|
|
7
7
|
* Boundaries:
|
|
8
|
-
* - REJECT (skip subtree) on nested [data-component] —
|
|
9
|
-
*
|
|
10
|
-
* even *visit* those nodes.
|
|
8
|
+
* - REJECT (skip subtree) on nested [data-component] — a parent component
|
|
9
|
+
* never processes directives owned by a nested child. Applied during the
|
|
10
|
+
* walk so we don't even *visit* those nodes.
|
|
11
11
|
* - <template> contents are not visited (browser TreeWalker default).
|
|
12
12
|
* `<template data-each>` itself IS visited and classified into scan.each;
|
|
13
|
-
* its children are processed by each.ts on every render
|
|
13
|
+
* its children are processed by each.ts on every render — fresh rows
|
|
14
|
+
* are wrapped in a per-row element and scanned via scanComponent.
|
|
14
15
|
*
|
|
15
16
|
* Hot-path notes:
|
|
16
17
|
* - We read `el.attributes` once and switch by suffix. No allocations per
|
|
@@ -27,8 +28,3 @@ import type { ScanIndex } from "../types";
|
|
|
27
28
|
* are free.
|
|
28
29
|
*/
|
|
29
30
|
export declare function scanComponent(root: Element): ScanIndex;
|
|
30
|
-
/**
|
|
31
|
-
* Scan a DocumentFragment (no-key each clone). Not cached — these fragments
|
|
32
|
-
* are temporary and re-cloned every render.
|
|
33
|
-
*/
|
|
34
|
-
export declare function scanFragment(frag: DocumentFragment): ScanIndex;
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*
|
|
22
22
|
* @module Micra
|
|
23
23
|
*/
|
|
24
|
-
export type { StateRecord, UnsubFn, EventHandler, FetchOptions, ComponentMethods, ComponentBuiltins, ComponentInstance, ComponentDefinition, } from './types';
|
|
24
|
+
export type { StateRecord, UnsubFn, EventHandler, EventPayload, EmitArgs, MicraEvents, FetchOptions, ComponentMethods, ComponentBuiltins, ComponentInstance, ComponentDefinition, } from './types';
|
|
25
25
|
export { FetchError } from './utils/fetch';
|
|
26
26
|
export { define, defineComponent, instances, registry, debug } from './core/registry';
|
|
27
27
|
export { mount } from './core/mount';
|
package/dist/micra.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Micra.js v2.
|
|
1
|
+
/* Micra.js v2.3.1 — https://github.com/micra-js/micra — MIT */
|
|
2
2
|
"use strict";
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -227,8 +227,9 @@ function off(event, handler) {
|
|
|
227
227
|
set.delete(handler);
|
|
228
228
|
if (set.size === 0) _bus.delete(event);
|
|
229
229
|
}
|
|
230
|
-
function emit(event,
|
|
230
|
+
function emit(event, ...args) {
|
|
231
231
|
var _a;
|
|
232
|
+
const payload = args[0];
|
|
232
233
|
(_a = _bus.get(event)) == null ? void 0 : _a.forEach((h) => {
|
|
233
234
|
try {
|
|
234
235
|
h(payload);
|
|
@@ -252,13 +253,14 @@ function createReactiveState(obj, schedule, onKey) {
|
|
|
252
253
|
}
|
|
253
254
|
function createScheduler(render) {
|
|
254
255
|
let pending = false;
|
|
256
|
+
const flush = () => {
|
|
257
|
+
pending = false;
|
|
258
|
+
render();
|
|
259
|
+
};
|
|
255
260
|
return function schedule() {
|
|
256
261
|
if (pending) return;
|
|
257
262
|
pending = true;
|
|
258
|
-
|
|
259
|
-
pending = false;
|
|
260
|
-
render();
|
|
261
|
-
});
|
|
263
|
+
queueMicrotask(flush);
|
|
262
264
|
};
|
|
263
265
|
}
|
|
264
266
|
|
|
@@ -324,7 +326,7 @@ function applyModel(el, key, rawState) {
|
|
|
324
326
|
const desired = stateVal == null ? "" : String(stateVal);
|
|
325
327
|
if (html.value !== desired) html.value = desired;
|
|
326
328
|
}
|
|
327
|
-
function applyDirectives(scan, state, rawState
|
|
329
|
+
function applyDirectives(scan, state, rawState) {
|
|
328
330
|
for (const b of scan.if) applyIf(b, state);
|
|
329
331
|
for (const b of scan.text) applyText(b.el, b.expr, state);
|
|
330
332
|
for (const b of scan.html) applyHtml(b.el, b.expr, state);
|
|
@@ -535,20 +537,6 @@ function scanComponent(root) {
|
|
|
535
537
|
}
|
|
536
538
|
return scan;
|
|
537
539
|
}
|
|
538
|
-
function scanFragment(frag) {
|
|
539
|
-
const scan = emptyScan();
|
|
540
|
-
const walker = document.createTreeWalker(
|
|
541
|
-
frag,
|
|
542
|
-
NodeFilter.SHOW_ELEMENT,
|
|
543
|
-
NESTED_COMPONENT_FILTER
|
|
544
|
-
);
|
|
545
|
-
let node = walker.nextNode();
|
|
546
|
-
while (node) {
|
|
547
|
-
classify(node, scan);
|
|
548
|
-
node = walker.nextNode();
|
|
549
|
-
}
|
|
550
|
-
return scan;
|
|
551
|
-
}
|
|
552
540
|
|
|
553
541
|
// src/dom/each.ts
|
|
554
542
|
function renderList(templates, state, rawState, instance, triggerKey) {
|
|
@@ -579,10 +567,28 @@ function renderList(templates, state, rawState, instance, triggerKey) {
|
|
|
579
567
|
if (keyAttr) {
|
|
580
568
|
renderKeyed(tmpl, items, keyAttr, marker, keyMap, state, rawState, instance, canSkipUnchanged);
|
|
581
569
|
} else {
|
|
582
|
-
renderNoKey(tmpl, items, marker, state, rawState, instance);
|
|
570
|
+
renderNoKey(tmpl, items, marker, state, rawState, instance, canSkipUnchanged);
|
|
583
571
|
}
|
|
584
572
|
}
|
|
585
573
|
}
|
|
574
|
+
function createRowNode(tmpl, state, instance) {
|
|
575
|
+
const frag = tmpl.content.cloneNode(true);
|
|
576
|
+
let node;
|
|
577
|
+
if (frag.childNodes.length === 1) {
|
|
578
|
+
node = frag.firstElementChild;
|
|
579
|
+
} else {
|
|
580
|
+
node = document.createElement("micra-each-item");
|
|
581
|
+
node.style.display = "contents";
|
|
582
|
+
node.append(frag);
|
|
583
|
+
}
|
|
584
|
+
const rowScan = scanComponent(node);
|
|
585
|
+
node.__micraScan = rowScan;
|
|
586
|
+
node._itemState = Object.create(state);
|
|
587
|
+
bindDataOn(rowScan.on, instance);
|
|
588
|
+
bindAtEvents(rowScan.atEvents, instance);
|
|
589
|
+
bindModels(rowScan.model, instance);
|
|
590
|
+
return node;
|
|
591
|
+
}
|
|
586
592
|
function renderKeyed(tmpl, items, keyAttr, marker, keyMap, state, rawState, instance, canSkipUnchanged) {
|
|
587
593
|
var _a;
|
|
588
594
|
const nextKeys = /* @__PURE__ */ new Set();
|
|
@@ -602,22 +608,8 @@ function renderKeyed(tmpl, items, keyAttr, marker, keyMap, state, rawState, inst
|
|
|
602
608
|
nextKeys.add(key);
|
|
603
609
|
let node = keyMap.get(key);
|
|
604
610
|
if (!node) {
|
|
605
|
-
|
|
606
|
-
if (frag.childNodes.length === 1) {
|
|
607
|
-
node = frag.firstElementChild;
|
|
608
|
-
} else {
|
|
609
|
-
node = document.createElement("micra-each-item");
|
|
610
|
-
node.style.display = "contents";
|
|
611
|
-
node.append(frag);
|
|
612
|
-
}
|
|
613
|
-
node.__micraKey = key;
|
|
611
|
+
node = createRowNode(tmpl, state, instance);
|
|
614
612
|
keyMap.set(key, node);
|
|
615
|
-
const rowScan2 = scanComponent(node);
|
|
616
|
-
node.__micraScan = rowScan2;
|
|
617
|
-
bindDataOn(rowScan2.on, instance);
|
|
618
|
-
bindAtEvents(rowScan2.atEvents, instance);
|
|
619
|
-
bindModels(rowScan2.model, instance);
|
|
620
|
-
node._itemState = Object.create(state);
|
|
621
613
|
} else if (canSkipUnchanged && node.__micraItem === item && node.__micraIndex === index) {
|
|
622
614
|
nextNodes.push(node);
|
|
623
615
|
continue;
|
|
@@ -629,7 +621,7 @@ function renderKeyed(tmpl, items, keyAttr, marker, keyMap, state, rawState, inst
|
|
|
629
621
|
itemState.index = index;
|
|
630
622
|
itemState.$index = index;
|
|
631
623
|
const rowScan = (_a = node.__micraScan) != null ? _a : node.__micraScan = scanComponent(node);
|
|
632
|
-
applyDirectives(rowScan, itemState, rawState
|
|
624
|
+
applyDirectives(rowScan, itemState, rawState);
|
|
633
625
|
nextNodes.push(node);
|
|
634
626
|
}
|
|
635
627
|
for (const [key, node] of keyMap) {
|
|
@@ -695,29 +687,50 @@ function reorderKeyed(nextNodes, prevList, marker) {
|
|
|
695
687
|
anchor = node;
|
|
696
688
|
}
|
|
697
689
|
}
|
|
698
|
-
function renderNoKey(tmpl, items, marker, state, rawState, instance) {
|
|
699
|
-
tmpl.__micraList
|
|
700
|
-
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
)
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
}
|
|
720
|
-
|
|
690
|
+
function renderNoKey(tmpl, items, marker, state, rawState, instance, canSkipUnchanged) {
|
|
691
|
+
const prevList = tmpl.__micraList;
|
|
692
|
+
const prevLen = prevList.length;
|
|
693
|
+
const nextLen = items.length;
|
|
694
|
+
const reuseLen = nextLen < prevLen ? nextLen : prevLen;
|
|
695
|
+
const nextList = new Array(nextLen);
|
|
696
|
+
for (let i = 0; i < reuseLen; i++) {
|
|
697
|
+
const node = prevList[i];
|
|
698
|
+
const item = items[i];
|
|
699
|
+
if (canSkipUnchanged && node.__micraItem === item && node.__micraIndex === i) {
|
|
700
|
+
nextList[i] = node;
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
node.__micraItem = item;
|
|
704
|
+
node.__micraIndex = i;
|
|
705
|
+
const itemState = node._itemState;
|
|
706
|
+
itemState.item = item;
|
|
707
|
+
itemState.index = i;
|
|
708
|
+
itemState.$index = i;
|
|
709
|
+
applyDirectives(node.__micraScan, itemState, rawState);
|
|
710
|
+
nextList[i] = node;
|
|
711
|
+
}
|
|
712
|
+
for (let i = nextLen; i < prevLen; i++) {
|
|
713
|
+
prevList[i].remove();
|
|
714
|
+
}
|
|
715
|
+
if (nextLen > prevLen) {
|
|
716
|
+
const frag = document.createDocumentFragment();
|
|
717
|
+
for (let i = prevLen; i < nextLen; i++) {
|
|
718
|
+
const node = createRowNode(tmpl, state, instance);
|
|
719
|
+
const item = items[i];
|
|
720
|
+
const itemState = node._itemState;
|
|
721
|
+
itemState.item = item;
|
|
722
|
+
itemState.index = i;
|
|
723
|
+
itemState.$index = i;
|
|
724
|
+
node.__micraItem = item;
|
|
725
|
+
node.__micraIndex = i;
|
|
726
|
+
applyDirectives(node.__micraScan, itemState, rawState);
|
|
727
|
+
nextList[i] = node;
|
|
728
|
+
frag.append(node);
|
|
729
|
+
}
|
|
730
|
+
const anchor = prevLen > 0 ? nextList[prevLen - 1] : marker;
|
|
731
|
+
anchor.after(frag);
|
|
732
|
+
}
|
|
733
|
+
tmpl.__micraList = nextList;
|
|
721
734
|
}
|
|
722
735
|
|
|
723
736
|
// src/dom/refs.ts
|
|
@@ -809,7 +822,7 @@ function mount(selector, definition) {
|
|
|
809
822
|
try {
|
|
810
823
|
const mRoot2 = root;
|
|
811
824
|
const scan = (_a2 = mRoot2.__micraScan) != null ? _a2 : mRoot2.__micraScan = scanComponent(root);
|
|
812
|
-
applyDirectives(scan, exprState, rawState
|
|
825
|
+
applyDirectives(scan, exprState, rawState);
|
|
813
826
|
renderList(scan.each, exprState, rawState, instance, triggerKey);
|
|
814
827
|
bindDataOn(scan.on, instance);
|
|
815
828
|
bindAtEvents(scan.atEvents, instance);
|