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/src/dom/scan.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
|
|
@@ -166,24 +167,3 @@ export function scanComponent(root: Element): ScanIndex {
|
|
|
166
167
|
return scan;
|
|
167
168
|
}
|
|
168
169
|
|
|
169
|
-
/**
|
|
170
|
-
* Scan a DocumentFragment (no-key each clone). Not cached — these fragments
|
|
171
|
-
* are temporary and re-cloned every render.
|
|
172
|
-
*/
|
|
173
|
-
export function scanFragment(frag: DocumentFragment): ScanIndex {
|
|
174
|
-
const scan = emptyScan();
|
|
175
|
-
|
|
176
|
-
const walker = document.createTreeWalker(
|
|
177
|
-
frag,
|
|
178
|
-
NodeFilter.SHOW_ELEMENT,
|
|
179
|
-
NESTED_COMPONENT_FILTER,
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
let node: Element | null = walker.nextNode() as Element | null;
|
|
183
|
-
while (node) {
|
|
184
|
-
classify(node, scan);
|
|
185
|
-
node = walker.nextNode() as Element | null;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return scan;
|
|
189
|
-
}
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -22,6 +22,53 @@ export type UnsubFn = () => void
|
|
|
22
22
|
/** Event bus handler. Generic `T` types the payload. */
|
|
23
23
|
export type EventHandler<T = unknown> = (payload: T) => void
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Type-safe event bus registry. Empty by default — augment it via
|
|
27
|
+
* declaration merging to type your application's events.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* declare module 'micra.js' {
|
|
31
|
+
* interface MicraEvents {
|
|
32
|
+
* 'cart:updated': { count: number }
|
|
33
|
+
* 'user:login': { id: number; name: string }
|
|
34
|
+
* 'modal:close': void
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* Micra.emit('cart:updated', { count: 3 }) // ✓ typed
|
|
39
|
+
* Micra.emit('cart:updated', { count: '3' }) // ✗ type error
|
|
40
|
+
* Micra.on('user:login', user => user.id) // user: { id, name }
|
|
41
|
+
*
|
|
42
|
+
* Events not present in the interface fall back to `unknown` payload —
|
|
43
|
+
* fully backward-compatible with untyped usage.
|
|
44
|
+
*/
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
46
|
+
export interface MicraEvents {}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the payload type for an event key. For keys registered in
|
|
50
|
+
* `MicraEvents` returns the declared payload; for any other string key
|
|
51
|
+
* returns `unknown` (preserving backward compatibility).
|
|
52
|
+
*/
|
|
53
|
+
export type EventPayload<K extends string> = K extends keyof MicraEvents
|
|
54
|
+
? MicraEvents[K]
|
|
55
|
+
: unknown
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Tuple of arguments passed to `emit` after the event name. When the
|
|
59
|
+
* payload type for a known event includes `undefined` (or the payload is
|
|
60
|
+
* declared as `void`), the argument is optional. For known events with a
|
|
61
|
+
* required payload, the argument is required. Unknown events accept any
|
|
62
|
+
* optional payload (backward compat).
|
|
63
|
+
*/
|
|
64
|
+
export type EmitArgs<K extends string> = K extends keyof MicraEvents
|
|
65
|
+
? [MicraEvents[K]] extends [void]
|
|
66
|
+
? [payload?: undefined]
|
|
67
|
+
: undefined extends MicraEvents[K]
|
|
68
|
+
? [payload?: MicraEvents[K]]
|
|
69
|
+
: [payload: MicraEvents[K]]
|
|
70
|
+
: [payload?: unknown]
|
|
71
|
+
|
|
25
72
|
/** Options for `this.fetch()`. For GET/HEAD extra keys become query params. */
|
|
26
73
|
export interface FetchOptions {
|
|
27
74
|
method?: string
|
|
@@ -71,10 +118,16 @@ export interface ComponentBuiltins<S extends StateRecord = StateRecord> {
|
|
|
71
118
|
prop<T>(name: string, defaultVal: T): T
|
|
72
119
|
/** Fetch helper: CSRF header, JSON body, query params, typed errors. */
|
|
73
120
|
fetch(url: string, options?: FetchOptions): Promise<unknown>
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Publish an event on the global bus.
|
|
123
|
+
* Payload is typed via the `MicraEvents` interface (augmentable).
|
|
124
|
+
*/
|
|
125
|
+
emit<K extends string>(event: K, ...args: EmitArgs<K>): void
|
|
126
|
+
/**
|
|
127
|
+
* Subscribe to the global bus. Subscription is auto-removed on destroy().
|
|
128
|
+
* Handler payload is typed via the `MicraEvents` interface (augmentable).
|
|
129
|
+
*/
|
|
130
|
+
on<K extends string>(event: K, handler: (payload: EventPayload<K>) => void): UnsubFn
|
|
78
131
|
}
|
|
79
132
|
|
|
80
133
|
/**
|
|
@@ -146,8 +199,6 @@ export interface MicraElement extends HTMLElement {
|
|
|
146
199
|
__micraModel?: true // data-model listener bound
|
|
147
200
|
__micraEvents?: true // data-on listeners bound
|
|
148
201
|
__micraAtBound?: true // @event shorthand bound (per-element)
|
|
149
|
-
__micraKey?: unknown // keyed-diff key
|
|
150
|
-
__micraEach?: true // belongs to a no-key each list
|
|
151
202
|
__micraScan?: ScanIndex // single-pass scan result (cached after 1st render)
|
|
152
203
|
__micraItem?: StateRecord // keyed row: last-rendered item ref (for skip check)
|
|
153
204
|
__micraIndex?: number // keyed row: last-rendered index (for skip check)
|
package/src/dom/query.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* src/dom/query.ts — DOM query helpers.
|
|
3
|
-
*
|
|
4
|
-
* LLM NOTE: These are utility functions with no side effects.
|
|
5
|
-
* queryOwn is the critical function that prevents a parent component from
|
|
6
|
-
* accidentally processing directives belonging to a nested child component.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* querySelectorAll wrapper — returns a typed array.
|
|
11
|
-
*/
|
|
12
|
-
export function queryAll(root: ParentNode, sel: string): Element[] {
|
|
13
|
-
return Array.from(root.querySelectorAll(sel))
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Like querySelectorAll, but EXCLUDES elements that live inside a nested
|
|
18
|
-
* `[data-component]` subtree.
|
|
19
|
-
*
|
|
20
|
-
* This is what prevents a parent component's render() from clobbering
|
|
21
|
-
* the DOM managed by a child component.
|
|
22
|
-
*
|
|
23
|
-
* LLM NOTE: The walk goes up parentElement until it hits `root` or null.
|
|
24
|
-
* If any ancestor (between el and root) has data-component, the element is
|
|
25
|
-
* owned by that nested component, not by root's component — so we skip it.
|
|
26
|
-
*/
|
|
27
|
-
export function queryOwn(root: Element, attr: string): Element[] {
|
|
28
|
-
return filterOwn(root, queryAll(root, `[${attr}]`))
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Like queryOwn but accepts an arbitrary CSS selector. Used by bindAtEvents
|
|
33
|
-
* which scans `*` for `@`-prefixed attribute names (no attribute selector exists
|
|
34
|
-
* for those).
|
|
35
|
-
*/
|
|
36
|
-
export function queryOwnAll(root: Element, sel: string): Element[] {
|
|
37
|
-
return filterOwn(root, queryAll(root, sel))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** @internal Shared subtree-ownership filter. */
|
|
41
|
-
function filterOwn(root: Element, els: Element[]): Element[] {
|
|
42
|
-
return els.filter(el => {
|
|
43
|
-
let node: Element | null = el.parentElement
|
|
44
|
-
while (node && node !== root) {
|
|
45
|
-
if (node.hasAttribute('data-component')) return false
|
|
46
|
-
node = node.parentElement
|
|
47
|
-
}
|
|
48
|
-
return true
|
|
49
|
-
})
|
|
50
|
-
}
|