micra.js 2.1.0 → 2.2.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 +244 -0
- package/README.md +39 -14
- package/dist/core/mount.d.ts +8 -2
- package/dist/core/reactive.d.ts +1 -1
- package/dist/core/registry.d.ts +13 -4
- package/dist/dom/directives.d.ts +11 -15
- package/dist/dom/each.d.ts +10 -7
- package/dist/dom/events.d.ts +15 -5
- package/dist/dom/refs.d.ts +5 -5
- package/dist/dom/scan.d.ts +34 -0
- package/dist/index.d.ts +1 -1
- package/dist/micra.cjs.js +302 -177
- package/dist/micra.cjs.js.map +4 -4
- package/dist/micra.esm.js +302 -177
- package/dist/micra.esm.js.map +4 -4
- package/dist/micra.js +302 -177
- package/dist/micra.js.map +4 -4
- package/dist/micra.min.js +2 -2
- package/dist/types.d.ts +67 -22
- package/llms-full.txt +600 -0
- package/llms.txt +148 -0
- package/package.json +11 -3
- package/src/core/mount.ts +136 -99
- package/src/core/reactive.ts +2 -1
- package/src/core/registry.ts +19 -9
- package/src/dom/directives.ts +39 -122
- package/src/dom/each.ts +133 -37
- package/src/dom/events.ts +23 -31
- package/src/dom/refs.ts +7 -7
- package/src/dom/scan.ts +189 -0
- package/src/index.ts +2 -0
- package/src/types.ts +80 -22
- package/src/utils/expr.ts +34 -21
package/dist/types.d.ts
CHANGED
|
@@ -23,14 +23,21 @@ export interface FetchOptions {
|
|
|
23
23
|
[key: string]: unknown;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
* `
|
|
26
|
+
* User-defined methods on a component definition. Any function-shaped property
|
|
27
|
+
* other than `state`, `onCreate`, `onDestroy` is treated as a method.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* LLM NOTE: this type is used as a structural HINT only — `M` in
|
|
30
|
+
* ComponentDefinition is unconstrained so TS can infer it from the literal
|
|
31
|
+
* without rejecting non-function siblings like `state`. The `[key: string]`
|
|
32
|
+
* shape here just documents intent.
|
|
33
|
+
*/
|
|
34
|
+
export type ComponentMethods = Record<string, (...args: any[]) => any>;
|
|
35
|
+
/**
|
|
36
|
+
* Built-in slots every instance gets: state, refs, $el, and the methods Micra
|
|
37
|
+
* itself injects (render, destroy, prop, fetch, emit, on). Kept separate from
|
|
38
|
+
* `M` so user methods can't accidentally shadow these names.
|
|
32
39
|
*/
|
|
33
|
-
export interface
|
|
40
|
+
export interface ComponentBuiltins<S extends StateRecord = StateRecord> {
|
|
34
41
|
/** The root DOM element this component is mounted on. */
|
|
35
42
|
readonly $el: HTMLElement;
|
|
36
43
|
/** Reactive state — any assignment triggers a batched re-render. */
|
|
@@ -58,11 +65,36 @@ export interface ComponentInstance<S extends StateRecord = StateRecord> {
|
|
|
58
65
|
/** Subscribe to the global bus. Subscription is auto-removed on destroy(). */
|
|
59
66
|
on<T = unknown>(event: string, handler: EventHandler<T>): UnsubFn;
|
|
60
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* The `this` context inside component methods and lifecycle hooks.
|
|
70
|
+
* `S` is inferred from the component's `state` object; `M` is inferred from
|
|
71
|
+
* the methods on the same definition object. Both `this.state.X` and
|
|
72
|
+
* `this.someMethod()` are fully typed inside method bodies.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* Micra.define('counter', {
|
|
76
|
+
* state: { count: 0 },
|
|
77
|
+
* inc() {
|
|
78
|
+
* this.state.count++ // this.state.count: number ✓
|
|
79
|
+
* this.dec() // this.dec: () => void ✓
|
|
80
|
+
* // this.foo() // ❌ Property 'foo' does not exist
|
|
81
|
+
* },
|
|
82
|
+
* dec() { this.state.count-- },
|
|
83
|
+
* })
|
|
84
|
+
*/
|
|
85
|
+
export type ComponentInstance<S extends StateRecord = StateRecord, M = ComponentMethods> = ComponentBuiltins<S> & M;
|
|
61
86
|
/**
|
|
62
87
|
* Component definition passed to `Micra.define` or `Micra.mount`.
|
|
63
88
|
*
|
|
64
|
-
* `S`
|
|
65
|
-
* `this: ComponentInstance<S>`
|
|
89
|
+
* Both `S` (state shape) and `M` (method set) are inferred from the literal.
|
|
90
|
+
* All methods and lifecycle hooks receive `this: ComponentInstance<S, M>` via
|
|
91
|
+
* `ThisType<>` — so `this.state.X` and `this.someMethod()` are both typed.
|
|
92
|
+
*
|
|
93
|
+
* LLM NOTE: `M` is intentionally unconstrained here. A constraint like
|
|
94
|
+
* `M extends ComponentMethods` would force every property in the literal to
|
|
95
|
+
* be a function — which would reject `state`. Without the constraint, TS
|
|
96
|
+
* structurally infers `M` as "everything in the literal except the known
|
|
97
|
+
* lifecycle/state keys", which is exactly what we want.
|
|
66
98
|
*
|
|
67
99
|
* @example
|
|
68
100
|
* Micra.define('counter', {
|
|
@@ -70,21 +102,20 @@ export interface ComponentInstance<S extends StateRecord = StateRecord> {
|
|
|
70
102
|
* inc() { this.state.count++ }, // this.state.count: number ✓
|
|
71
103
|
* })
|
|
72
104
|
*/
|
|
73
|
-
export type ComponentDefinition<S extends StateRecord = StateRecord> = {
|
|
105
|
+
export type ComponentDefinition<S extends StateRecord = StateRecord, M = ComponentMethods> = {
|
|
74
106
|
/** Initial flat state. Becomes reactive on mount. */
|
|
75
107
|
state?: S;
|
|
76
108
|
/**
|
|
77
109
|
* Called once after mount in a microtask — safe for async data fetching.
|
|
78
110
|
* @example async onCreate() { this.state.data = await this.fetch('/api/data') }
|
|
79
111
|
*/
|
|
80
|
-
onCreate
|
|
112
|
+
onCreate?(): void | Promise<void>;
|
|
81
113
|
/**
|
|
82
114
|
* Called on destroy — clean up DOM listeners, timers, etc.
|
|
83
115
|
* Event bus subscriptions added via `this.on()` are cleaned up automatically.
|
|
84
116
|
*/
|
|
85
|
-
onDestroy
|
|
86
|
-
|
|
87
|
-
} & ThisType<ComponentInstance<S>>;
|
|
117
|
+
onDestroy?(): void;
|
|
118
|
+
} & M & ThisType<ComponentInstance<S, M>>;
|
|
88
119
|
/**
|
|
89
120
|
* @internal Extended HTMLElement with Micra bookkeeping slots.
|
|
90
121
|
*/
|
|
@@ -94,7 +125,10 @@ export interface MicraElement extends HTMLElement {
|
|
|
94
125
|
__micraAtBound?: true;
|
|
95
126
|
__micraKey?: unknown;
|
|
96
127
|
__micraEach?: true;
|
|
97
|
-
|
|
128
|
+
__micraScan?: ScanIndex;
|
|
129
|
+
__micraItem?: StateRecord;
|
|
130
|
+
__micraIndex?: number;
|
|
131
|
+
_itemState?: StateRecord;
|
|
98
132
|
}
|
|
99
133
|
/**
|
|
100
134
|
* @internal A DOM listener tracked for cleanup on destroy().
|
|
@@ -110,7 +144,7 @@ export interface TrackedListener {
|
|
|
110
144
|
export interface MicraTemplate extends HTMLTemplateElement {
|
|
111
145
|
__micraMarker?: Comment;
|
|
112
146
|
__micraNodes: Map<unknown, MicraElement>;
|
|
113
|
-
__micraList:
|
|
147
|
+
__micraList: MicraElement[];
|
|
114
148
|
__micraNoKeyWarned?: true;
|
|
115
149
|
}
|
|
116
150
|
/**
|
|
@@ -139,13 +173,15 @@ export interface CachedPairBinding {
|
|
|
139
173
|
pairs: ReadonlyArray<readonly [string, string]>;
|
|
140
174
|
}
|
|
141
175
|
/**
|
|
142
|
-
* @internal
|
|
143
|
-
* This is the core of the performance
|
|
176
|
+
* @internal Single-pass scan result — built once per Element via one TreeWalker
|
|
177
|
+
* traversal, reused every render. This is the core of the performance
|
|
178
|
+
* optimization: instead of 10+ querySelectorAll calls per render, the scanner
|
|
179
|
+
* classifies every directive/event/ref attribute in a single DOM walk.
|
|
144
180
|
*
|
|
145
|
-
* LLM NOTE:
|
|
146
|
-
*
|
|
181
|
+
* LLM NOTE: ScanIndex is built lazily on first render and stored on
|
|
182
|
+
* `el.__micraScan`. Subsequent renders skip the scan entirely.
|
|
147
183
|
*/
|
|
148
|
-
export interface
|
|
184
|
+
export interface ScanIndex {
|
|
149
185
|
text: CachedBinding[];
|
|
150
186
|
html: CachedBinding[];
|
|
151
187
|
if: CachedIfBinding[];
|
|
@@ -153,13 +189,22 @@ export interface DirectiveCache {
|
|
|
153
189
|
bind: CachedPairBinding[];
|
|
154
190
|
model: CachedBinding[];
|
|
155
191
|
class: CachedPairBinding[];
|
|
192
|
+
each: Element[];
|
|
193
|
+
on: Element[];
|
|
194
|
+
atEvents: Element[];
|
|
195
|
+
refs: Element[];
|
|
156
196
|
}
|
|
157
197
|
/**
|
|
158
198
|
* @internal Full instance as seen inside the runtime — extends the public
|
|
159
|
-
*
|
|
199
|
+
* built-ins with private bookkeeping slots and an index signature for
|
|
160
200
|
* dynamic method dispatch.
|
|
201
|
+
*
|
|
202
|
+
* Note: internally we don't carry the user-method generic `M`. Internal modules
|
|
203
|
+
* dispatch methods by string name (`instance[methodName]()`), which is what
|
|
204
|
+
* the index signature is for. The public `mount()` return value re-projects
|
|
205
|
+
* to `ComponentInstance<S, M>` so callers get full type inference.
|
|
161
206
|
*/
|
|
162
|
-
export interface InternalInstance<S extends StateRecord = StateRecord> extends
|
|
207
|
+
export interface InternalInstance<S extends StateRecord = StateRecord> extends ComponentBuiltins<S> {
|
|
163
208
|
__micraSubs?: UnsubFn[];
|
|
164
209
|
__micraListeners?: TrackedListener[];
|
|
165
210
|
__micraDestroyed?: true;
|