@nisli/core 0.55.0 → 0.57.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 +58 -1
- package/dist/bundler/component.js +409 -0
- package/dist/bundler/component.js.map +7 -0
- package/dist/bundler/context.js +27 -0
- package/dist/bundler/context.js.map +7 -0
- package/dist/bundler/diagnostics.js +55 -0
- package/dist/bundler/diagnostics.js.map +7 -0
- package/dist/bundler/disposable.js +9 -0
- package/dist/bundler/disposable.js.map +7 -0
- package/dist/bundler/element-context.js +47 -0
- package/dist/bundler/element-context.js.map +7 -0
- package/dist/bundler/emitter.js +70 -0
- package/dist/bundler/emitter.js.map +7 -0
- package/dist/bundler/hmr/registry.js +72 -0
- package/dist/bundler/hmr/registry.js.map +7 -0
- package/dist/bundler/hmr/transform.js +30 -0
- package/dist/bundler/hmr/transform.js.map +7 -0
- package/dist/bundler/index.js +103 -0
- package/dist/bundler/index.js.map +7 -0
- package/dist/bundler/injector.js +75 -0
- package/dist/bundler/injector.js.map +7 -0
- package/dist/bundler/lifecycle.js +56 -0
- package/dist/bundler/lifecycle.js.map +7 -0
- package/dist/bundler/projection.js +92 -0
- package/dist/bundler/projection.js.map +7 -0
- package/dist/bundler/query.js +302 -0
- package/dist/bundler/query.js.map +7 -0
- package/dist/bundler/ref.js +16 -0
- package/dist/bundler/ref.js.map +7 -0
- package/dist/bundler/resource.js +99 -0
- package/dist/bundler/resource.js.map +7 -0
- package/dist/bundler/settle.js +43 -0
- package/dist/bundler/settle.js.map +7 -0
- package/dist/bundler/signal.js +403 -0
- package/dist/bundler/signal.js.map +7 -0
- package/dist/bundler/template-audit.js +162 -0
- package/dist/bundler/template-audit.js.map +7 -0
- package/dist/bundler/template.js +847 -0
- package/dist/bundler/template.js.map +7 -0
- package/dist/bundler/view-transition.js +20 -0
- package/dist/bundler/view-transition.js.map +7 -0
- package/dist/bundler/vite-hmr/plugin.js +32 -0
- package/dist/bundler/vite-hmr/plugin.js.map +7 -0
- package/dist/bundler/vite-hmr/runtime.js +8 -0
- package/dist/bundler/vite-hmr/runtime.js.map +7 -0
- package/dist/index.js +2607 -44
- package/dist/index.js.map +7 -1
- package/dist/index.production.js +2 -0
- package/dist/index.production.js.map +6 -0
- package/dist/{diagnostics.d.ts → types/diagnostics.d.ts} +8 -15
- package/dist/{index.d.ts → types/index.d.ts} +1 -0
- package/dist/{template-audit.d.ts → types/template-audit.d.ts} +4 -5
- package/dist/{template.d.ts → types/template.d.ts} +3 -15
- package/package.json +12 -9
- package/dist/component.js +0 -633
- package/dist/component.js.map +0 -1
- package/dist/context.js +0 -55
- package/dist/context.js.map +0 -1
- package/dist/diagnostics.js +0 -118
- package/dist/diagnostics.js.map +0 -1
- package/dist/disposable.js +0 -8
- package/dist/disposable.js.map +0 -1
- package/dist/element-context.js +0 -108
- package/dist/element-context.js.map +0 -1
- package/dist/emitter.js +0 -91
- package/dist/emitter.js.map +0 -1
- package/dist/hmr/registry.js +0 -136
- package/dist/hmr/registry.js.map +0 -1
- package/dist/hmr/transform.js +0 -52
- package/dist/hmr/transform.js.map +0 -1
- package/dist/injector.js +0 -118
- package/dist/injector.js.map +0 -1
- package/dist/lifecycle.js +0 -117
- package/dist/lifecycle.js.map +0 -1
- package/dist/projection.js +0 -179
- package/dist/projection.js.map +0 -1
- package/dist/query.js +0 -429
- package/dist/query.js.map +0 -1
- package/dist/ref.js +0 -43
- package/dist/ref.js.map +0 -1
- package/dist/resource.js +0 -129
- package/dist/resource.js.map +0 -1
- package/dist/settle.js +0 -106
- package/dist/settle.js.map +0 -1
- package/dist/signal.js +0 -645
- package/dist/signal.js.map +0 -1
- package/dist/template-audit.js +0 -246
- package/dist/template-audit.js.map +0 -1
- package/dist/template.js +0 -1218
- package/dist/template.js.map +0 -1
- package/dist/view-transition.js +0 -98
- package/dist/view-transition.js.map +0 -1
- package/dist/vite-hmr/plugin.js +0 -62
- package/dist/vite-hmr/plugin.js.map +0 -1
- package/dist/vite-hmr/runtime.js +0 -17
- package/dist/vite-hmr/runtime.js.map +0 -1
- /package/dist/{component.d.ts → types/component.d.ts} +0 -0
- /package/dist/{context.d.ts → types/context.d.ts} +0 -0
- /package/dist/{disposable.d.ts → types/disposable.d.ts} +0 -0
- /package/dist/{element-context.d.ts → types/element-context.d.ts} +0 -0
- /package/dist/{emitter.d.ts → types/emitter.d.ts} +0 -0
- /package/dist/{hmr → types/hmr}/registry.d.ts +0 -0
- /package/dist/{hmr → types/hmr}/transform.d.ts +0 -0
- /package/dist/{injector.d.ts → types/injector.d.ts} +0 -0
- /package/dist/{lifecycle.d.ts → types/lifecycle.d.ts} +0 -0
- /package/dist/{projection.d.ts → types/projection.d.ts} +0 -0
- /package/dist/{query.d.ts → types/query.d.ts} +0 -0
- /package/dist/{ref.d.ts → types/ref.d.ts} +0 -0
- /package/dist/{resource.d.ts → types/resource.d.ts} +0 -0
- /package/dist/{settle.d.ts → types/settle.d.ts} +0 -0
- /package/dist/{signal.d.ts → types/signal.d.ts} +0 -0
- /package/dist/{view-transition.d.ts → types/view-transition.d.ts} +0 -0
- /package/dist/{vite-hmr → types/vite-hmr}/plugin.d.ts +0 -0
- /package/dist/{vite-hmr → types/vite-hmr}/runtime.d.ts +0 -0
package/README.md
CHANGED
|
@@ -144,6 +144,48 @@ With no native `setHTML()` and no registered hook, the binding throws `N107`.
|
|
|
144
144
|
It fails closed by design: nisli never silently downgrades untrusted markup to
|
|
145
145
|
`innerHTML`, and never bundles a sanitizer of its own.
|
|
146
146
|
|
|
147
|
+
## Diagnostics
|
|
148
|
+
|
|
149
|
+
Development diagnostics report stable codes such as `[nisli N102]` for an
|
|
150
|
+
unknown event. The published package gives bundlers standard
|
|
151
|
+
`process.env.NODE_ENV` guards around optional diagnostics and their bookkeeping.
|
|
152
|
+
Vite production builds and esbuild browser bundles with full minification remove
|
|
153
|
+
that code automatically. For other bundlers, use their normal production
|
|
154
|
+
configuration that replaces `process.env.NODE_ENV` with `"production"` and
|
|
155
|
+
eliminates dead code. No Nisli plugin or custom flag is required. Unbundled Node
|
|
156
|
+
consumers use `NODE_ENV=production` to disable development checks.
|
|
157
|
+
|
|
158
|
+
For native browser ESM, serve the package's `dist/index.js` (development) or
|
|
159
|
+
`dist/index.production.js` (production). Both are complete, dependency-free
|
|
160
|
+
browser modules with no `process` global. Map the root import once for the whole
|
|
161
|
+
application; component source stays the same:
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<script type="importmap">
|
|
165
|
+
{ "imports": { "@nisli/core": "/vendor/nisli/index.production.js" } }
|
|
166
|
+
</script>
|
|
167
|
+
<script type="module" src="/app.js"></script>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Here `/vendor/nisli/index.production.js` is a copy of the installed package's
|
|
171
|
+
`dist/index.production.js`. Switch the mapping to `index.js` for development.
|
|
172
|
+
Keep all modules on the same core URL: loading both artifacts creates separate
|
|
173
|
+
reactive and injection state. The package root is for bundlers/Node; a raw
|
|
174
|
+
browser import must use one of the browser artifacts.
|
|
175
|
+
|
|
176
|
+
`setDevMode(false)` suppresses available development diagnostics;
|
|
177
|
+
`setDevMode(true)` enables them and `setDevMode(null)` restores the artifact's
|
|
178
|
+
default. Suppression does not shrink a development artifact. **Production
|
|
179
|
+
artifacts cannot re-enable removed diagnostics**, including from tests; use a
|
|
180
|
+
development artifact when asserting them. This replaces the pre-0.57 runtime
|
|
181
|
+
probe contract. `import.meta.env.PROD` alone is no longer a mode override.
|
|
182
|
+
|
|
183
|
+
Production still enforces template safety (`N105`–`N107`), query keys (`N602`),
|
|
184
|
+
effect loop disposal, and failure containment. Setup/mount failures still emit
|
|
185
|
+
`N401`/`N402`, stamp the host, and dispatch `nisli-error`; asynchronous effect
|
|
186
|
+
rejections and event-handler failures remain caught and reported. Development
|
|
187
|
+
diagnostics disappearing does not make production failures silent.
|
|
188
|
+
|
|
147
189
|
## Explicit resource management
|
|
148
190
|
|
|
149
191
|
Standalone Nisli disposables support `using` on runtimes with native explicit
|
|
@@ -199,6 +241,20 @@ Keep async work outside the callback — the page is frozen during capture — a
|
|
|
199
241
|
use the returned handle (`finished`, `ready`, `skipTransition()`) for hard
|
|
200
242
|
opt-outs and for superseding an in-flight transition.
|
|
201
243
|
|
|
244
|
+
Animating a keyed `each()` list needs one thing spelled out: the
|
|
245
|
+
`<each-item>` wrapper is `display: contents`, and a box-less element cannot be
|
|
246
|
+
captured, so `view-transition-name` must sit on the item's painted child — the
|
|
247
|
+
element your item template actually renders. Put it there and `each()`'s stable
|
|
248
|
+
per-key DOM identity does the rest, with no generated names:
|
|
249
|
+
|
|
250
|
+
```css
|
|
251
|
+
.card {
|
|
252
|
+
view-transition-name: match-element; /* identity-keyed, nothing to bookkeep */
|
|
253
|
+
view-transition-class: card; /* styles every card as one group */
|
|
254
|
+
}
|
|
255
|
+
::view-transition-group(.card) { animation-duration: 200ms; }
|
|
256
|
+
```
|
|
257
|
+
|
|
202
258
|
## Core capabilities
|
|
203
259
|
|
|
204
260
|
- Fine-grained reactivity: `signal`, `computed`, `effect`, `untrack`, `flush`,
|
|
@@ -209,7 +265,8 @@ opt-outs and for superseding an in-flight transition.
|
|
|
209
265
|
`class:*`, branded `html:inner` (trusted or sanitizer-routed), refs, and
|
|
210
266
|
dynamic HTML tags through `el()`.
|
|
211
267
|
- Stable control flow: lazy `when()` branches and keyed `each()` lists that
|
|
212
|
-
preserve focus, scroll position, and component state
|
|
268
|
+
preserve focus, scroll position, and component state, moved atomically with
|
|
269
|
+
`moveBefore()` where the engine has it.
|
|
213
270
|
- Two scopes of dependency injection: app-wide singleton services with
|
|
214
271
|
`inject`/`provide`, and portal-safe subtree state with `createContext`.
|
|
215
272
|
- Lifecycle and resilience: `onMount`, `onCleanup`, `useHostEvent`, automatic
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { signal, effect, setContextHook, isSignal, untrack } from "./signal.js";
|
|
2
|
+
import { runWithContext, hasContext, getCurrentComponent } from "./context.js";
|
|
3
|
+
import { runMountCallbacks } from "./lifecycle.js";
|
|
4
|
+
import { diag, formatDiag, isDev } from "./diagnostics.js";
|
|
5
|
+
const propsReadKeys = process.env.NODE_ENV !== "production" ? /* @__PURE__ */ new WeakMap() : void 0;
|
|
6
|
+
const completedSetups = process.env.NODE_ENV !== "production" ? /* @__PURE__ */ new WeakSet() : void 0;
|
|
7
|
+
const echoedProps = process.env.NODE_ENV !== "production" ? /* @__PURE__ */ new WeakMap() : void 0;
|
|
8
|
+
setContextHook(() => {
|
|
9
|
+
if (hasContext()) {
|
|
10
|
+
const comp = getCurrentComponent();
|
|
11
|
+
return (disposer) => comp.addDisposer(disposer);
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
});
|
|
15
|
+
const camelToKebab = (s) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
16
|
+
const declType = (decl) => typeof decl === "string" ? decl : decl.type;
|
|
17
|
+
const declAttrName = (key, decl) => typeof decl === "object" && decl.attr ? decl.attr : camelToKebab(key);
|
|
18
|
+
function resolveAttrValue(decl, raw) {
|
|
19
|
+
const t = declType(decl);
|
|
20
|
+
if (t === "boolean") {
|
|
21
|
+
const def = typeof decl === "object" && "default" in decl && decl.default !== void 0 ? decl.default : false;
|
|
22
|
+
if (raw === null) return def;
|
|
23
|
+
return raw !== "false";
|
|
24
|
+
}
|
|
25
|
+
if (t === "number") {
|
|
26
|
+
const def = typeof decl === "object" && "default" in decl ? decl.default : void 0;
|
|
27
|
+
if (raw === null) return def;
|
|
28
|
+
const n = Number(raw);
|
|
29
|
+
return Number.isNaN(n) ? def : n;
|
|
30
|
+
}
|
|
31
|
+
if (raw === null) {
|
|
32
|
+
return typeof decl === "object" && "default" in decl ? decl.default : void 0;
|
|
33
|
+
}
|
|
34
|
+
return raw;
|
|
35
|
+
}
|
|
36
|
+
class ComponentHostImpl {
|
|
37
|
+
element;
|
|
38
|
+
disposers = [];
|
|
39
|
+
setupComplete = false;
|
|
40
|
+
constructor(element) {
|
|
41
|
+
this.element = element;
|
|
42
|
+
}
|
|
43
|
+
addDisposer(fn) {
|
|
44
|
+
this.disposers.push(fn);
|
|
45
|
+
}
|
|
46
|
+
dispose() {
|
|
47
|
+
for (const fn of this.disposers) {
|
|
48
|
+
try {
|
|
49
|
+
fn();
|
|
50
|
+
} catch (_) {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
this.disposers.length = 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function createPropsProxy() {
|
|
57
|
+
const signals = /* @__PURE__ */ new Map();
|
|
58
|
+
const getOrCreate = (key, initialValue) => {
|
|
59
|
+
let s = signals.get(key);
|
|
60
|
+
if (!s) {
|
|
61
|
+
s = signal(initialValue);
|
|
62
|
+
signals.set(key, s);
|
|
63
|
+
}
|
|
64
|
+
return s;
|
|
65
|
+
};
|
|
66
|
+
const props = new Proxy({}, {
|
|
67
|
+
get(_target, key) {
|
|
68
|
+
if (typeof key !== "string") return void 0;
|
|
69
|
+
if (process.env.NODE_ENV !== "production") propsReadKeys.get(props).add(key);
|
|
70
|
+
return getOrCreate(key);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
if (process.env.NODE_ENV !== "production") propsReadKeys.set(props, /* @__PURE__ */ new Set());
|
|
74
|
+
const setProperty = (key, value) => {
|
|
75
|
+
const s = getOrCreate(key, value);
|
|
76
|
+
s.value = value;
|
|
77
|
+
};
|
|
78
|
+
return { props, setProperty };
|
|
79
|
+
}
|
|
80
|
+
const firstDefineSites = process.env.NODE_ENV !== "production" ? /* @__PURE__ */ new Map() : void 0;
|
|
81
|
+
function captureDefineSite() {
|
|
82
|
+
const stack = new Error().stack;
|
|
83
|
+
if (!stack) return "(site unavailable)";
|
|
84
|
+
const lines = stack.split("\n");
|
|
85
|
+
for (let i = 1; i < lines.length; i++) {
|
|
86
|
+
const line = lines[i].trim();
|
|
87
|
+
if (!line) continue;
|
|
88
|
+
if (line.includes("captureDefineSite") || /[/\\]component\.(?:ts|js)\b/.test(line)) continue;
|
|
89
|
+
return line;
|
|
90
|
+
}
|
|
91
|
+
return "(site unavailable)";
|
|
92
|
+
}
|
|
93
|
+
function component(tagName, setup, ...[options]) {
|
|
94
|
+
const attrEntries = /* @__PURE__ */ new Map();
|
|
95
|
+
const attrEntriesByKey = /* @__PURE__ */ new Map();
|
|
96
|
+
if (options?.attrs) {
|
|
97
|
+
for (const key of Object.keys(options.attrs)) {
|
|
98
|
+
const decl = options.attrs[key];
|
|
99
|
+
const attrName = declAttrName(key, decl);
|
|
100
|
+
const entry = { key, attrName, decl };
|
|
101
|
+
attrEntries.set(attrName, entry);
|
|
102
|
+
attrEntriesByKey.set(key, entry);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const observedList = Array.from(attrEntries.keys());
|
|
106
|
+
class FrameworkComponent extends HTMLElement {
|
|
107
|
+
_host = null;
|
|
108
|
+
_propsProxy = null;
|
|
109
|
+
_mounted = false;
|
|
110
|
+
_templateResult = null;
|
|
111
|
+
_errorTemplateResult = null;
|
|
112
|
+
_teardownScheduled = false;
|
|
113
|
+
// Props explicitly set via _setProp (factory / direct assignment) are
|
|
114
|
+
// "pinned": an explicit prop wins, so attribute writes no longer overwrite
|
|
115
|
+
// it. (Precedence ruling: prop-pins.)
|
|
116
|
+
_pinned = /* @__PURE__ */ new Set();
|
|
117
|
+
// Guards our own removeAttribute() during 'forward' relocation from
|
|
118
|
+
// re-entering attributeChangedCallback.
|
|
119
|
+
_relocating = false;
|
|
120
|
+
/** The browser reads this once at define time. Empty → no callbacks. */
|
|
121
|
+
static get observedAttributes() {
|
|
122
|
+
return observedList;
|
|
123
|
+
}
|
|
124
|
+
constructor() {
|
|
125
|
+
super();
|
|
126
|
+
this._propsProxy = createPropsProxy();
|
|
127
|
+
}
|
|
128
|
+
/** Write one declared attribute onto its prop signal, honoring pinning. */
|
|
129
|
+
_applyAttr(entry, raw) {
|
|
130
|
+
if (declType(entry.decl) === "forward") {
|
|
131
|
+
if (raw !== null) {
|
|
132
|
+
if (!this._pinned.has(entry.key)) {
|
|
133
|
+
this._propsProxy.setProperty(entry.key, raw);
|
|
134
|
+
}
|
|
135
|
+
this._relocating = true;
|
|
136
|
+
this.removeAttribute(entry.attrName);
|
|
137
|
+
this._relocating = false;
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (this._pinned.has(entry.key)) return;
|
|
142
|
+
this._propsProxy.setProperty(entry.key, resolveAttrValue(entry.decl, raw));
|
|
143
|
+
}
|
|
144
|
+
/** Seed initial prop values from current attributes (incl. defaults). */
|
|
145
|
+
_seedAttrs() {
|
|
146
|
+
for (const entry of attrEntries.values()) {
|
|
147
|
+
this._applyAttr(entry, this.getAttribute(entry.attrName));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
attributeChangedCallback(name, _old, next) {
|
|
151
|
+
if (this._relocating) return;
|
|
152
|
+
const entry = attrEntries.get(name);
|
|
153
|
+
if (entry) this._applyAttr(entry, next);
|
|
154
|
+
}
|
|
155
|
+
connectedCallback() {
|
|
156
|
+
if (this._mounted) return;
|
|
157
|
+
this._mounted = true;
|
|
158
|
+
untrack(() => {
|
|
159
|
+
const host = new ComponentHostImpl(this);
|
|
160
|
+
this._host = host;
|
|
161
|
+
if (attrEntries.size) this._seedAttrs();
|
|
162
|
+
if (this.childNodes.length) {
|
|
163
|
+
this.__capturedChildren = Array.from(this.childNodes);
|
|
164
|
+
}
|
|
165
|
+
let phase = "setup";
|
|
166
|
+
try {
|
|
167
|
+
runWithContext(host, () => {
|
|
168
|
+
this._templateResult = setup(
|
|
169
|
+
this._propsProxy.props,
|
|
170
|
+
this
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
if (this._templateResult) {
|
|
174
|
+
mountTemplate(this, this._templateResult, host);
|
|
175
|
+
}
|
|
176
|
+
const sweepHost = this;
|
|
177
|
+
if (sweepHost.__nisliProjectionSweep) {
|
|
178
|
+
const sweep = sweepHost.__nisliProjectionSweep;
|
|
179
|
+
sweepHost.__nisliProjectionSweep = void 0;
|
|
180
|
+
sweep();
|
|
181
|
+
}
|
|
182
|
+
phase = "mount";
|
|
183
|
+
runMountCallbacks(host);
|
|
184
|
+
if (process.env.NODE_ENV !== "production") completedSetups.add(this);
|
|
185
|
+
this.removeAttribute("data-nisli-error");
|
|
186
|
+
} catch (err) {
|
|
187
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
188
|
+
const code = phase === "setup" ? "N401" : "N402";
|
|
189
|
+
diag(code, `<${tagName}> ${phase} failed: ${error.message}`, {
|
|
190
|
+
tag: tagName,
|
|
191
|
+
phase,
|
|
192
|
+
error
|
|
193
|
+
});
|
|
194
|
+
this.__nisliProjectionSweep = void 0;
|
|
195
|
+
if (this._templateResult?.dispose) {
|
|
196
|
+
this._templateResult.dispose();
|
|
197
|
+
}
|
|
198
|
+
this._templateResult = null;
|
|
199
|
+
host.dispose();
|
|
200
|
+
this._host = null;
|
|
201
|
+
this.replaceChildren();
|
|
202
|
+
this.setAttribute("data-nisli-error", code);
|
|
203
|
+
if (options?.onError) {
|
|
204
|
+
try {
|
|
205
|
+
const fallback = options.onError(error, this);
|
|
206
|
+
if (typeof fallback === "string") {
|
|
207
|
+
this.innerHTML = fallback;
|
|
208
|
+
} else {
|
|
209
|
+
this._errorTemplateResult = fallback;
|
|
210
|
+
mountTemplate(this, fallback);
|
|
211
|
+
}
|
|
212
|
+
} catch (_) {
|
|
213
|
+
if (this._errorTemplateResult?.dispose) {
|
|
214
|
+
this._errorTemplateResult.dispose();
|
|
215
|
+
}
|
|
216
|
+
this._errorTemplateResult = null;
|
|
217
|
+
this.innerHTML = `<div style="color:red;padding:4px">Error in <${tagName}></div>`;
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
this.innerHTML = `<div style="color:red;padding:4px">Error in <${tagName}></div>`;
|
|
221
|
+
}
|
|
222
|
+
this.dispatchEvent(
|
|
223
|
+
new CustomEvent("nisli-error", {
|
|
224
|
+
bubbles: true,
|
|
225
|
+
composed: true,
|
|
226
|
+
detail: { code, tag: tagName, phase, message: error.message }
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
disconnectedCallback() {
|
|
233
|
+
if (this._teardownScheduled) return;
|
|
234
|
+
this._teardownScheduled = true;
|
|
235
|
+
queueMicrotask(() => {
|
|
236
|
+
this._teardownScheduled = false;
|
|
237
|
+
if (this.isConnected) return;
|
|
238
|
+
this._teardownNow();
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Atomic DOM moves preserve this component's position-independent state,
|
|
243
|
+
* so the platform-level lifecycle is a no-op. ADR 0023's deferred
|
|
244
|
+
* disconnect teardown remains the fallback for append-based moves.
|
|
245
|
+
*/
|
|
246
|
+
connectedMoveCallback() {
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Synchronously dispose the current component scope and restore any
|
|
250
|
+
* projected light-DOM children as direct host children. Normal disconnect
|
|
251
|
+
* calls this after the move-detection microtask; HMR calls it directly so
|
|
252
|
+
* reconnect cannot be defeated by the `_mounted` guard (issues 0001/0002).
|
|
253
|
+
*/
|
|
254
|
+
_teardownNow() {
|
|
255
|
+
this._mounted = false;
|
|
256
|
+
if (process.env.NODE_ENV !== "production") completedSetups.delete(this);
|
|
257
|
+
if (this._templateResult?.dispose) {
|
|
258
|
+
this._templateResult.dispose();
|
|
259
|
+
}
|
|
260
|
+
this._templateResult = null;
|
|
261
|
+
if (this._errorTemplateResult?.dispose) {
|
|
262
|
+
this._errorTemplateResult.dispose();
|
|
263
|
+
}
|
|
264
|
+
this._errorTemplateResult = null;
|
|
265
|
+
if (this._host) {
|
|
266
|
+
this._host.dispose();
|
|
267
|
+
this._host = null;
|
|
268
|
+
}
|
|
269
|
+
const captured = this.__capturedChildren ?? [];
|
|
270
|
+
this.replaceChildren(...captured);
|
|
271
|
+
}
|
|
272
|
+
/** @internal — transport-agnostic HMR in-place remount hook. */
|
|
273
|
+
_remount() {
|
|
274
|
+
this._teardownScheduled = false;
|
|
275
|
+
this._teardownNow();
|
|
276
|
+
this.connectedCallback();
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Set a prop value. Called by the factory and by direct property assignment.
|
|
280
|
+
* The Proxy intercepts and creates/updates backing signals.
|
|
281
|
+
*
|
|
282
|
+
* Precedence = DEFINED-write-pins (ADR 0025 item 3): a defined
|
|
283
|
+
* value pins the key so a declared attribute no longer overwrites it. An
|
|
284
|
+
* explicit `undefined` (e.g. a factory spreading an unset optional prop —
|
|
285
|
+
* `{...opts}` fires _setProp(key, undefined) per the Object.entries walk)
|
|
286
|
+
* must NOT pin; it falls back to the declared attribute/default, matching
|
|
287
|
+
* attr()'s coalesce. For a declared attribute, that fallback re-resolves
|
|
288
|
+
* from the current attribute, keeping declared-default booleans
|
|
289
|
+
* non-undefined.
|
|
290
|
+
*/
|
|
291
|
+
_setProp(key, value) {
|
|
292
|
+
if (process.env.NODE_ENV !== "production" && completedSetups.has(this) && value !== void 0 && isDev() && !attrEntriesByKey.has(key) && !propsReadKeys.get(this._propsProxy.props).has(key) && !echoedProps.get(this)?.has(key)) {
|
|
293
|
+
const echoed = echoedProps.get(this) ?? /* @__PURE__ */ new Set();
|
|
294
|
+
echoed.add(key);
|
|
295
|
+
echoedProps.set(this, echoed);
|
|
296
|
+
diag(
|
|
297
|
+
"N202",
|
|
298
|
+
`<${tagName}> prop '${key}' was set but setup() never read it \u2014 misspelled or unsupported prop?`,
|
|
299
|
+
{ tag: tagName, key }
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
if (value === void 0) {
|
|
303
|
+
this._pinned.delete(key);
|
|
304
|
+
const entry = attrEntriesByKey.get(key);
|
|
305
|
+
if (entry) {
|
|
306
|
+
const raw = this.getAttribute(entry.attrName);
|
|
307
|
+
if (raw === null && declType(entry.decl) === "forward") {
|
|
308
|
+
this._propsProxy?.setProperty(key, resolveAttrValue(entry.decl, null));
|
|
309
|
+
} else {
|
|
310
|
+
this._applyAttr(entry, raw);
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
this._pinned.add(key);
|
|
316
|
+
}
|
|
317
|
+
this._propsProxy?.setProperty(key, value);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Read a prop's backing signal (framework-internal). Used by children()
|
|
321
|
+
* to fold the factory `children` prop into the projection slot (ADR 0025
|
|
322
|
+
* item 1) without a new public read API.
|
|
323
|
+
*/
|
|
324
|
+
_propSignal(key) {
|
|
325
|
+
return this._propsProxy.props[key];
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Whether a prop is currently pinned by a defined factory/property write
|
|
329
|
+
* (framework-internal, like _propSignal). The attribute-as-truth open-state
|
|
330
|
+
* pattern needs this as its controlled-mode discriminator: a declared
|
|
331
|
+
* 'boolean' prop is never undefined (absent → false), so pin state is the
|
|
332
|
+
* ONLY way a component can tell "parent drives this" from "the attribute
|
|
333
|
+
* drives this" — e.g. setOpen() must not write the open attribute while a
|
|
334
|
+
* controlled parent has it pinned, or the DOM attribute diverges from the
|
|
335
|
+
* pinned state until the next prop write.
|
|
336
|
+
*/
|
|
337
|
+
_isPinned(key) {
|
|
338
|
+
return this._pinned.has(key);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (!customElements.get(tagName)) {
|
|
342
|
+
customElements.define(tagName, FrameworkComponent);
|
|
343
|
+
if (process.env.NODE_ENV !== "production" && isDev()) firstDefineSites.set(tagName, captureDefineSite());
|
|
344
|
+
} else if (process.env.NODE_ENV !== "production" && isDev() && !setup.__nisliHmr) {
|
|
345
|
+
const first = firstDefineSites.get(tagName) ?? "(unknown site \u2014 defined outside component(), or before dev mode was enabled)";
|
|
346
|
+
throw new Error(formatDiag(
|
|
347
|
+
"N201",
|
|
348
|
+
`duplicate define for <${tagName}>. First defined ${first}; the second definition site is this error's own stack. The first definition stays live (first-wins) \u2014 rename one of the components.`
|
|
349
|
+
));
|
|
350
|
+
}
|
|
351
|
+
const factory = (props, hostAttrs) => {
|
|
352
|
+
return {
|
|
353
|
+
__type: "factory",
|
|
354
|
+
tagName,
|
|
355
|
+
props,
|
|
356
|
+
hostAttrs
|
|
357
|
+
};
|
|
358
|
+
};
|
|
359
|
+
return factory;
|
|
360
|
+
}
|
|
361
|
+
function ensureSignal(value) {
|
|
362
|
+
if (isSignal(value)) return value;
|
|
363
|
+
return signal(value);
|
|
364
|
+
}
|
|
365
|
+
function mountTemplate(host, result, hostImpl) {
|
|
366
|
+
if (result && typeof result === "object" && "mount" in result && typeof result.mount === "function") {
|
|
367
|
+
result.mount(host);
|
|
368
|
+
} else if (result && typeof result === "object" && "__type" in result) {
|
|
369
|
+
const factoryResult = result;
|
|
370
|
+
const el = document.createElement(factoryResult.tagName);
|
|
371
|
+
for (const [key, raw] of Object.entries(factoryResult.props)) {
|
|
372
|
+
const sig = ensureSignal(raw);
|
|
373
|
+
el._setProp(key, sig.value);
|
|
374
|
+
const unsub = sig.subscribe((newVal) => {
|
|
375
|
+
el._setProp(key, newVal);
|
|
376
|
+
});
|
|
377
|
+
if (hostImpl) {
|
|
378
|
+
hostImpl.addDisposer(unsub);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (factoryResult.hostAttrs?.class != null) {
|
|
382
|
+
const classSig = ensureSignal(factoryResult.hostAttrs.class);
|
|
383
|
+
applyHostClass(el, "", classSig.value);
|
|
384
|
+
let prevClass = classSig.value;
|
|
385
|
+
const unsub = classSig.subscribe((newClass) => {
|
|
386
|
+
applyHostClass(el, prevClass, newClass);
|
|
387
|
+
prevClass = newClass;
|
|
388
|
+
});
|
|
389
|
+
if (hostImpl) {
|
|
390
|
+
hostImpl.addDisposer(unsub);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
host.appendChild(el);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function applyHostClass(el, prev, next) {
|
|
397
|
+
const prevClasses = prev ? prev.split(/\s+/).filter(Boolean) : [];
|
|
398
|
+
const nextClasses = next ? next.split(/\s+/).filter(Boolean) : [];
|
|
399
|
+
for (const cls of prevClasses) {
|
|
400
|
+
if (!nextClasses.includes(cls)) el.classList.remove(cls);
|
|
401
|
+
}
|
|
402
|
+
for (const cls of nextClasses) {
|
|
403
|
+
if (!prevClasses.includes(cls)) el.classList.add(cls);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
export {
|
|
407
|
+
component
|
|
408
|
+
};
|
|
409
|
+
//# sourceMappingURL=component.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/component.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * component.ts \u2014 Web component shell with typed props and factory composition\n *\n * `component()` is the single entry point for defining components.\n * It registers a custom element and returns a typed factory function.\n *\n * Component authors write a setup function that receives typed props\n * (each wrapped as a Signal) and optionally the host element.\n * The setup function returns a TemplateResult from html`...`.\n *\n * The class internals (connectedCallback, etc.) are completely hidden.\n */\n\nimport { signal, effect, setContextHook, isSignal, untrack, type Signal, type ReadonlySignal } from './signal.js';\nimport { runWithContext, hasContext, getCurrentComponent, type ComponentHost } from './context.js';\nimport type { TemplateResult } from './template.js';\nimport { runMountCallbacks } from './lifecycle.js';\nimport { diag, formatDiag, isDev } from './diagnostics.js';\n\n// Diagnostic state is external to production component/props objects. Each\n// allocation and use is statically gated so the entire maps disappear (0048).\nconst propsReadKeys = process.env.NODE_ENV !== 'production'\n ? new WeakMap<object, Set<string>>() : undefined;\nconst completedSetups = process.env.NODE_ENV !== 'production'\n ? new WeakSet<HTMLElement>() : undefined;\nconst echoedProps = process.env.NODE_ENV !== 'production'\n ? new WeakMap<HTMLElement, Set<string>>() : undefined;\n\n// Wire up effect auto-disposal: when effect() is called during component setup,\n// auto-register the dispose function with the component host.\nsetContextHook(() => {\n if (hasContext()) {\n const comp = getCurrentComponent();\n return (disposer: () => void) => comp.addDisposer(disposer);\n }\n return null;\n});\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Narrows one prop's reactive value to the runtime type its `attrs`\n * declaration guarantees (ADR 0025 candidate (b)). Only the two kinds core\n * reconciles to a NON-`undefined` runtime value narrow; everything else is\n * left as the author's `T` (= `P[K]`):\n *\n * - `'boolean'` / `{ type:'boolean' }` (any default) \u2192 `boolean` \u2014 a declared\n * boolean is absent\u2192`false`, never `undefined`.\n * - `{ type:'number'; default }` \u2192 `number` \u2014 the default guarantees a value.\n * - `'number'` / `{ type:'number' }` WITHOUT a default \u2192 `number | undefined`.\n * - `'string'` / `{ type:'string' }` / `'forward'` / undeclared \u2192 `T` unchanged\n * (a string attr is genuinely absent\u2192`undefined`; forwarding does not touch\n * the value type).\n *\n * SOUND FALLBACK on an incompatible declaration: the narrowed runtime type is\n * applied ONLY when the declared kind and `NonNullable<T>` (the prop's author\n * type) agree EXACTLY \u2014 mutual assignability, BOTH directions. A one-way check\n * (`boolean extends NonNullable<T>`) is unsound: it would still narrow a\n * `boolean | string` prop to `boolean` (silently dropping `string`), and a\n * one-way check the other way would widen a literal `true`/`1` prop. So a\n * declaration is honored only when `NonNullable<T>` IS the kind \u2014 `boolean` for\n * a boolean declaration, `number` for a numeric one \u2014 via {@link IsExactKind}.\n * Every genuine mismatch or partial overlap (`boolean | string` declared\n * `'boolean'`, `'boolean'` on a `string` prop, a numeric decl on a boolean prop,\n * a literal `true`/`1` prop) falls back to `T` unchanged \u2014 never a compile-time\n * lie about the value. The whole registry declares props as exactly `boolean` /\n * `number`, so it narrows as before; only unions/literals/mismatches are refused.\n *\n * `D` is the declaration at that key (or `undefined` when the key is absent\n * from the `attrs` map, in which case the caller passes `T` through directly).\n */\nexport type AttrValueType<T, D> =\n D extends 'boolean' ? (IsExactKind<T, boolean> extends true ? boolean : T)\n : D extends { type: 'boolean' } ? (IsExactKind<T, boolean> extends true ? boolean : T)\n : D extends { type: 'number'; default: number } ? (IsExactKind<T, number> extends true ? number : T)\n : D extends 'number' ? (IsExactKind<T, number> extends true ? number | undefined : T)\n : D extends { type: 'number' } ? (IsExactKind<T, number> extends true ? number | undefined : T)\n : T;\n\n/**\n * `true` iff `NonNullable<T>` is EXACTLY `K` \u2014 mutually assignable in both\n * directions (`NonNullable<T> extends K` AND `K extends NonNullable<T>`). The\n * tuple wrappers block distribution so `boolean` (`= true | false`) is compared\n * whole: `boolean | string` is NOT exactly `boolean` (fails the first arm), and\n * `true` is NOT exactly `boolean` (fails the second), so both are refused.\n */\ntype IsExactKind<T, K> =\n [NonNullable<T>] extends [K] ? ([K] extends [NonNullable<T>] ? true : false) : false;\n\n/**\n * Maps a props interface to reactive signals, narrowed by the `attrs`\n * declaration `A` (ADR 0025 candidate (b)).\n *\n * Each prop `P[K]` becomes `Signal<AttrValueType<P[K], A[K]>>` when `K` is\n * declared in `A`, otherwise `Signal<P[K]>`. So a declared `'boolean'` types as\n * `Signal<boolean>` (not `Signal<boolean | undefined>`), retiring the\n * registry's `as boolean` stopgap. `A` defaults to `{}` (no declared keys \u2192\n * every prop unchanged), so `ReactiveProps<P>` keeps its pre-candidate-(b)\n * meaning and existing single-argument uses are unaffected.\n *\n * Optionality is stripped (`-?`): the props Proxy always returns a signal,\n * even for props never set \u2014 an optional prop `variant?: T` is\n * `Signal<T | undefined>`, not `Signal<T> | undefined`.\n */\nexport type ReactiveProps<P, A extends ComponentAttrs<P> = {}> = {\n readonly [K in keyof P]-?: Signal<K extends keyof A ? AttrValueType<P[K], A[K]> : P[K]>;\n};\n\n/**\n * The setup function signature. Receives reactive props (narrowed by the\n * `attrs` declaration `A`) and the host element. Returns a TemplateResult to be\n * mounted into the component.\n */\nexport type SetupFunction<P, A extends ComponentAttrs<P> = {}> = (\n props: ReactiveProps<P, A>,\n host: HTMLElement,\n) => TemplateResult;\n\n/**\n * Accepts either a Signal<T> (reactive) or a plain T (static, auto-wrapped).\n * This eliminates the need to wrap static values in `signal()` at call sites.\n * See ADR 0009 Gap 4.\n */\nexport type PropInput<T> = T | Signal<T> | ReadonlySignal<T>;\n\n/**\n * Host-level attributes that every factory accepts, independent of component props.\n * `class` is applied to the host element via classList \u2014 it does not collide with\n * the component's own class attribute or class:name directives.\n * See ADR 0009 Gap 3.\n */\nexport interface HostAttrs {\n class?: PropInput<string>;\n}\n\n/**\n * A factory function for type-safe component composition.\n * Props accept both Signal<T> (reactive) and plain T (static, auto-wrapped).\n * An optional second argument provides host-level attributes (class).\n */\nexport type ComponentFactory<P> = (\n props: { [K in keyof P]: PropInput<P[K]> },\n hostAttrs?: HostAttrs,\n) => TemplateResult;\n\n/**\n * Opt-in attribute reactivity declaration (ADR 0025 item 3).\n *\n * Declares how a host attribute maps onto a prop signal, subsuming userland\n * `attr()`/`boolAttr()`/`forwardedAttr()`. Unlike those (which read the\n * attribute once at setup), a declared attribute is `observedAttributes`-wired,\n * so `setAttribute()` AFTER mount writes the prop signal LIVE.\n *\n * - `'string'` \u2014 the attribute's string value (absent \u2192 `undefined`).\n * - `'boolean'` \u2014 OUR boolean semantics: bare/any value \u2192 `true`, literal\n * `\"false\"` \u2192 `false`, absent \u2192 `false`.\n * - `'number'` \u2014 `Number(raw)`; absent \u2192 the default (else `undefined`); a\n * non-numeric value behaves as absent \u2192 the default (else `undefined`), so a\n * garbage attribute never propagates `NaN` into layout math.\n * - `{ type: 'boolean', default: true }` \u2014 as above but absent \u2192 the default\n * (so a default-`true` flag can be opted out with `attr=\"false\"`).\n * - `{ type: 'string', default: '\u2026' }` \u2014 string with an absent-value default.\n * - `{ type: 'number', default: 0 }` \u2014 number with an absent-value default.\n * - `'forward'` \u2014 string semantics PLUS relocation: the attribute is removed\n * from the (layout-transparent) host so `id`/`name` live only on the inner\n * control (native form participation), matching `forwardedAttr()`.\n *\n * The observed attribute name is the kebab-case of the prop key (`className` \u2194\n * `class-name`, `showOutsideDays` \u2194 `show-outside-days`, `id` \u2194 `id`). The\n * object forms accept an `attr` override for props whose native attribute is\n * NOT the kebab derivation \u2014 e.g. `readOnly: { type: 'boolean', attr: 'readonly' }`\n * (the native boolean attribute is `readonly`, not `read-only`). (v1.1)\n */\nexport type AttrDecl =\n | 'string'\n | 'boolean'\n | 'number'\n | 'forward'\n | { type: 'boolean'; default?: boolean; attr?: string }\n | { type: 'string'; default?: string; attr?: string }\n | { type: 'number'; default?: number; attr?: string };\n\n/**\n * The `attrs` declaration map for a props interface `P`: prop keys \u2192 attribute\n * behavior. This is the type an `attrs` object satisfies; capture its literal\n * type with `typeof` and pass it as `component`'s second type argument to\n * unlock declared-type narrowing (ADR 0025 candidate (b)):\n *\n * ```ts\n * const attrs = { checked: 'boolean' } satisfies ComponentAttrs<SwitchProps>;\n * const Switch = component<SwitchProps, typeof attrs>('ui-switch', setup, { attrs });\n * // inside setup: props.checked.value is `boolean`, not `boolean | undefined`\n * ```\n */\nexport type ComponentAttrs<P> = Partial<Record<Extract<keyof P, string>, AttrDecl>>;\n\n/** Options for component() registration */\nexport interface ComponentOptions<P = unknown> {\n /** Custom error fallback renderer */\n onError?: (error: Error, host: HTMLElement) => TemplateResult | string;\n /**\n * Opt-in attribute reactivity (ADR 0025 item 3). Maps prop keys\n * to attribute declarations. Omitted entirely \u2192 zero cost (empty\n * `observedAttributes`, no `attributeChangedCallback` work).\n */\n attrs?: ComponentAttrs<P>;\n}\n\n// \u2500\u2500 Attribute-reactivity helpers (ADR 0025 item 3) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface AttrEntry {\n /** camelCase prop key. */\n key: string;\n /** kebab-case observed attribute name. */\n attrName: string;\n decl: AttrDecl;\n}\n\nconst camelToKebab = (s: string): string =>\n s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);\n\nconst declType = (decl: AttrDecl): 'string' | 'boolean' | 'number' | 'forward' =>\n typeof decl === 'string' ? decl : decl.type;\n\n/** The observed attribute name: an explicit `attr` override, else kebab(key). */\nconst declAttrName = (key: string, decl: AttrDecl): string =>\n typeof decl === 'object' && decl.attr ? decl.attr : camelToKebab(key);\n\n/** Resolve a raw attribute string (or null when absent) to the prop value. */\nfunction resolveAttrValue(decl: AttrDecl, raw: string | null): unknown {\n const t = declType(decl);\n if (t === 'boolean') {\n const def =\n typeof decl === 'object' && 'default' in decl && decl.default !== undefined\n ? decl.default\n : false;\n if (raw === null) return def; // absent \u2192 declared default (else false)\n return raw !== 'false'; // bare/any \u2192 true, literal \"false\" \u2192 false\n }\n if (t === 'number') {\n const def = typeof decl === 'object' && 'default' in decl ? decl.default : undefined;\n if (raw === null) return def; // absent \u2192 declared default (else undefined)\n const n = Number(raw);\n // Garbage behaves as ABSENT \u2192 the declared default (else undefined). A\n // default exists to guarantee non-undefined; letting garbage fall to\n // literal undefined would reintroduce exactly what the default prevents.\n return Number.isNaN(n) ? def : n;\n }\n // 'string' | 'forward'\n if (raw === null) {\n return typeof decl === 'object' && 'default' in decl ? decl.default : undefined;\n }\n return raw;\n}\n\n// \u2500\u2500 Internal component host \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nclass ComponentHostImpl implements ComponentHost {\n element: HTMLElement;\n private disposers: (() => void)[] = [];\n private setupComplete = false;\n\n constructor(element: HTMLElement) {\n this.element = element;\n }\n\n addDisposer(fn: () => void): void {\n this.disposers.push(fn);\n }\n\n dispose(): void {\n for (const fn of this.disposers) {\n try { fn(); } catch (_) { /* swallow disposal errors */ }\n }\n this.disposers.length = 0;\n }\n}\n\n// \u2500\u2500 Prop signal management via Proxy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Creates a Proxy-based props object that lazily creates signals\n * for each property on first access. When a parent sets a property\n * on the element, the Proxy intercepts it and updates (or creates)\n * the backing signal.\n */\nfunction createPropsProxy<P>(): {\n props: ReactiveProps<P>;\n setProperty: (key: string, value: unknown) => void;\n} {\n const signals = new Map<string, Signal<unknown>>();\n\n // Keys the component has READ through the proxy, accumulated over the\n // COMPONENT LIFETIME (the proxy is constructor-owned and survives\n // remounts/HMR swaps, so a re-setup never false-positives). This is the\n // truth source for the unknown-prop echo (N202, ADR 0030.2): a post-mount\n // write to a never-read key is attribution noise, not state. A component\n // that stashes `props` and reads a key dynamically AFTER such a write gets\n // one stale echo \u2014 accepted; the echo is attribution-only. The dev-only\n // propsReadKeys map above owns this bookkeeping, outside production objects.\n const getOrCreate = (key: string, initialValue?: unknown): Signal<unknown> => {\n let s = signals.get(key);\n if (!s) {\n s = signal(initialValue);\n signals.set(key, s);\n }\n return s;\n };\n\n const props = new Proxy({} as ReactiveProps<P>, {\n get(_target, key) {\n // Symbol probes (well-known symbols, thenable checks) are not props.\n if (typeof key !== 'string') return undefined;\n if (process.env.NODE_ENV !== 'production') propsReadKeys!.get(props)!.add(key);\n return getOrCreate(key);\n },\n });\n\n if (process.env.NODE_ENV !== 'production') propsReadKeys!.set(props, new Set());\n\n const setProperty = (key: string, value: unknown) => {\n const s = getOrCreate(key, value);\n s.value = value;\n };\n\n return { props, setProperty };\n}\n\n// \u2500\u2500 Duplicate-define attribution (N201, dev only) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** First definition site per tag, captured in dev for N201 attribution. */\nconst firstDefineSites = process.env.NODE_ENV !== 'production'\n ? new Map<string, string>() : undefined;\n\n/**\n * Best-effort caller frame for N201: the first stack line that is neither the\n * Error header nor a frame inside this module. Dev-only; a user file that is\n * itself named `component.ts` gets skipped too (best-effort attribution, never\n * behavior).\n */\nfunction captureDefineSite(): string {\n const stack = new Error().stack;\n if (!stack) return '(site unavailable)';\n const lines = stack.split('\\n');\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i]!.trim();\n if (!line) continue;\n if (line.includes('captureDefineSite') || /[/\\\\]component\\.(?:ts|js)\\b/.test(line)) continue;\n return line;\n }\n return '(site unavailable)';\n}\n\n// \u2500\u2500 component() \u2014 the main entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Define a web component with typed props and a setup function.\n *\n * Returns a typed factory function for compile-time-safe composition.\n *\n * ```ts\n * interface TaskItemProps {\n * task: Task;\n * selected: boolean;\n * }\n *\n * const TaskItem = component<TaskItemProps>('task-item', (props, host) => {\n * const title = computed(() => props.task.value.title);\n * return html`<div>${title}</div>`;\n * });\n *\n * // Factory usage:\n * TaskItem({ task: taskSignal, selected: isSelected })\n * ```\n */\nexport function component<\n P extends object = Record<string, never>,\n A extends ComponentAttrs<P> = {},\n>(\n tagName: string,\n setup: SetupFunction<P, A>,\n // When `A` is explicitly given a non-empty attrs literal (the narrowing\n // opt-in), the options argument \u2014 carrying `attrs: A` \u2014 is REQUIRED: a\n // `component<P, typeof attrs>(tag, setup)` that types the props as narrowed but\n // never actually wires the attrs at runtime would be a compile-time lie. When\n // `A` is `{}` (the legacy default, no second type arg), options stays optional\n // exactly as before. `attrs` in that legacy branch is still validated as\n // `ComponentAttrs<P>` via `ComponentOptions<P>`.\n ...[options]: [keyof A] extends [never]\n ? [options?: ComponentOptions<P>]\n : [options: ComponentOptions<P> & { attrs: A }]\n): ComponentFactory<P> {\n // Attribute-reactivity wiring (ADR 0025 item 3). Computed once\n // per component definition; empty when no `attrs` declared (\u2192 zero cost).\n const attrEntries = new Map<string, AttrEntry>(); // by attrName\n const attrEntriesByKey = new Map<string, AttrEntry>(); // by prop key\n if (options?.attrs) {\n for (const key of Object.keys(options.attrs)) {\n const decl = (options.attrs as Record<string, AttrDecl>)[key]!;\n const attrName = declAttrName(key, decl);\n const entry: AttrEntry = { key, attrName, decl };\n attrEntries.set(attrName, entry);\n attrEntriesByKey.set(key, entry);\n }\n }\n const observedList = Array.from(attrEntries.keys());\n\n // The custom element class\n class FrameworkComponent extends HTMLElement {\n private _host: ComponentHostImpl | null = null;\n private _propsProxy: ReturnType<typeof createPropsProxy<P>> | null = null;\n private _mounted = false;\n private _templateResult: TemplateResult | null = null;\n private _errorTemplateResult: TemplateResult | null = null;\n private _teardownScheduled = false;\n // Props explicitly set via _setProp (factory / direct assignment) are\n // \"pinned\": an explicit prop wins, so attribute writes no longer overwrite\n // it. (Precedence ruling: prop-pins.)\n private _pinned = new Set<string>();\n // Guards our own removeAttribute() during 'forward' relocation from\n // re-entering attributeChangedCallback.\n private _relocating = false;\n\n /** The browser reads this once at define time. Empty \u2192 no callbacks. */\n static get observedAttributes(): string[] {\n return observedList;\n }\n\n constructor() {\n super();\n this._propsProxy = createPropsProxy<P>();\n }\n\n /** Write one declared attribute onto its prop signal, honoring pinning. */\n private _applyAttr(entry: AttrEntry, raw: string | null): void {\n if (declType(entry.decl) === 'forward') {\n // Relocate off the layout-transparent host (no duplicate id/name), but\n // only adopt the attribute's value when no explicit prop pinned it. A\n // null `raw` here (absent OR already-relocated) is a NO-OP: the seed\n // and attributeChangedCallback must not clobber an adopted value. The\n // explicit unpin path (in _setProp) handles clearing.\n if (raw !== null) {\n if (!this._pinned.has(entry.key)) {\n this._propsProxy!.setProperty(entry.key, raw);\n }\n this._relocating = true;\n this.removeAttribute(entry.attrName);\n this._relocating = false;\n }\n return;\n }\n if (this._pinned.has(entry.key)) return; // explicit prop wins\n this._propsProxy!.setProperty(entry.key, resolveAttrValue(entry.decl, raw));\n }\n\n /** Seed initial prop values from current attributes (incl. defaults). */\n private _seedAttrs(): void {\n for (const entry of attrEntries.values()) {\n this._applyAttr(entry, this.getAttribute(entry.attrName));\n }\n }\n\n attributeChangedCallback(name: string, _old: string | null, next: string | null): void {\n if (this._relocating) return;\n const entry = attrEntries.get(name);\n if (entry) this._applyAttr(entry, next);\n }\n\n connectedCallback() {\n if (this._mounted) return; // guard against re-connection\n this._mounted = true;\n\n // Isolate activeObserver: child component setup signal reads must NOT\n // leak into a parent effect's dependency set. Without this, a parent\n // effect running replaceChildren/appendChild triggers child\n // connectedCallback synchronously, and child signal reads would be\n // tracked as parent dependencies \u2014 causing infinite re-render loops.\n // See ADR 0008 Gap 1 / ADR 0009.\n untrack(() => {\n const host = new ComponentHostImpl(this);\n this._host = host;\n\n // Canonical pre-setup order: SEED \u2192 CAPTURE \u2192 CONTEXT (then setup).\n //\n // SEED (ADR 0025 item 3): write prop signals from current\n // attributes (incl. declared defaults and 'forward' relocation) so setup\n // sees correct initial values. No-op when no attrs are declared.\n if (attrEntries.size) this._seedAttrs();\n\n // CAPTURE (ADR 0025 item 1): snapshot the host's light-DOM\n // children so children() can project them. Snapshot only \u2014 children()\n // removes/projects on demand \u2014 so non-projecting components are\n // untouched. Zero-cost when the host has no light DOM.\n if (this.childNodes.length) {\n (this as unknown as { __capturedChildren?: Node[] }).__capturedChildren =\n Array.from(this.childNodes);\n }\n\n // Phase attribution for the SHARED containment boundary (ADR 0030.2\n // T6): 'setup' (N401) until the template is committed, 'mount' (N402)\n // once onMount callbacks begin. Both phases route through the ONE\n // catch below \u2014 runMountCallbacks no longer swallows (lifecycle.ts).\n let phase: 'setup' | 'mount' = 'setup';\n try {\n // CONTEXT is established by runWithContext(host, \u2026) below; setup then\n // runs with seed + capture already in place.\n runWithContext(host, () => {\n // The Proxy is runtime-untyped; the declared narrowing (A) is a\n // compile-time view the setup function requested (ADR 0025 (b)).\n this._templateResult = setup(\n this._propsProxy!.props as ReactiveProps<P, A>,\n this,\n );\n });\n\n // Mount the template result\n if (this._templateResult) {\n mountTemplate(this, this._templateResult, host);\n }\n\n // PROJECTION PHASE (ADR 0030.2 \u00A73): if setup called children(), run\n // its late-children sweep scheduling NOW \u2014 after the template\n // committed, BEFORE any onMount callback. The rendered-roots\n // snapshot therefore never depends on where children() sat relative\n // to onMount() registrations (the 0025 batch-3B ordering trap), and\n // the rule becomes universal: any host child appearing after this\n // point \u2014 late parser/innerHTML content OR an onMount host-append \u2014\n // classifies as projected content.\n const sweepHost = this as unknown as { __nisliProjectionSweep?: () => void };\n if (sweepHost.__nisliProjectionSweep) {\n const sweep = sweepHost.__nisliProjectionSweep;\n sweepHost.__nisliProjectionSweep = undefined; // consumed once per cycle\n sweep();\n }\n\n // Run onMount() callbacks \u2014 DOM is now committed\n phase = 'mount';\n runMountCallbacks(host);\n\n if (process.env.NODE_ENV !== 'production') completedSetups!.add(this);\n // T6: a SUCCESSFUL (re-)setup \u2014 including _remount(), the documented\n // boundary reset \u2014 removes the failure stamp.\n this.removeAttribute('data-nisli-error');\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n const code = phase === 'setup' ? 'N401' : 'N402';\n diag(code, `<${tagName}> ${phase} failed: ${error.message}`, {\n tag: tagName,\n phase,\n error,\n });\n\n // Setup may already have registered effects/lifecycle cleanup, or\n // mountTemplate() may have partially installed bindings before\n // throwing \u2014 and a mount-phase throw arrives with the whole scope\n // live. Tear the partial scope down BEFORE mounting the error\n // fallback so no hidden work survives behind it (issue 0010; the\n // onMount phase now shares these exact semantics per T6). An\n // unconsumed projection sweep hook is part of that partial scope.\n (this as unknown as { __nisliProjectionSweep?: () => void }).__nisliProjectionSweep =\n undefined;\n if (this._templateResult?.dispose) {\n this._templateResult.dispose();\n }\n this._templateResult = null;\n host.dispose();\n this._host = null;\n this.replaceChildren();\n\n // T6 \u2014 failure is a DOM fact. The attribute is the DURABLE channel:\n // `querySelectorAll('[data-nisli-error]')` answers \"which component\n // failed\" long after the fact, with no listener installed in\n // advance. Client-runtime only \u2014 SSG failures stay build failures.\n this.setAttribute('data-nisli-error', code);\n\n if (options?.onError) {\n try {\n const fallback = options.onError(error, this);\n if (typeof fallback === 'string') {\n this.innerHTML = fallback;\n } else {\n this._errorTemplateResult = fallback;\n mountTemplate(this, fallback);\n }\n } catch (_) {\n if (this._errorTemplateResult?.dispose) {\n this._errorTemplateResult.dispose();\n }\n this._errorTemplateResult = null;\n this.innerHTML = `<div style=\"color:red;padding:4px\">Error in <${tagName}></div>`;\n }\n } else {\n this.innerHTML = `<div style=\"color:red;padding:4px\">Error in <${tagName}></div>`;\n }\n\n // T6 \u2014 the EVENT is the live channel: one document-level listener\n // observes every contained failure as it happens. Dispatched LAST so\n // listeners see the settled DOM (stamp + fallback). Bubbling is a\n // DOM-tree walk, so CSS (display:contents transparent hosts) never\n // affects delivery \u2014 but a component mounted into a DETACHED\n // fragment has no path to document and the event is swallowed; the\n // attribute above is the durable record for those (ADR 0030.2 \u00A78).\n this.dispatchEvent(\n new CustomEvent('nisli-error', {\n bubbles: true,\n composed: true,\n detail: { code, tag: tagName, phase, message: error.message },\n }),\n );\n }\n });\n }\n\n disconnectedCallback() {\n // A same-tick remove+reinsert is a MOVE, not a removal: per WHATWG,\n // append-based moves fire disconnected+connected (only moveBefore()\n // preserves state). Moves happen routinely inside the framework \u2014\n // each() keyed reorders, light-DOM projection of nested components \u2014\n // and re-running setup on them duplicates rendered output and loses\n // state. Defer teardown one microtask and skip it if the element is\n // connected again; connectedCallback's _mounted guard then makes the\n // whole move a no-op. True removals still dispose, one microtask\n // later. See ADR 0023.\n if (this._teardownScheduled) return;\n this._teardownScheduled = true;\n queueMicrotask(() => {\n this._teardownScheduled = false;\n if (this.isConnected) return;\n this._teardownNow();\n });\n }\n\n /**\n * Atomic DOM moves preserve this component's position-independent state,\n * so the platform-level lifecycle is a no-op. ADR 0023's deferred\n * disconnect teardown remains the fallback for append-based moves.\n */\n connectedMoveCallback(): void {}\n\n /**\n * Synchronously dispose the current component scope and restore any\n * projected light-DOM children as direct host children. Normal disconnect\n * calls this after the move-detection microtask; HMR calls it directly so\n * reconnect cannot be defeated by the `_mounted` guard (issues 0001/0002).\n */\n private _teardownNow(): void {\n this._mounted = false;\n if (process.env.NODE_ENV !== 'production') completedSetups!.delete(this);\n if (this._templateResult?.dispose) {\n this._templateResult.dispose();\n }\n this._templateResult = null;\n if (this._errorTemplateResult?.dispose) {\n this._errorTemplateResult.dispose();\n }\n this._errorTemplateResult = null;\n if (this._host) {\n this._host.dispose();\n this._host = null;\n }\n\n // children() records the plain light-DOM nodes it owns during cleanup.\n // Replacing the rendered tree with those nodes preserves author content\n // for a later true reconnect/HMR setup; components without projection\n // simply become empty before the fresh setup appends its new subtree.\n const captured = (this as unknown as { __capturedChildren?: Node[] })\n .__capturedChildren ?? [];\n this.replaceChildren(...captured);\n }\n\n /** @internal \u2014 transport-agnostic HMR in-place remount hook. */\n _remount(): void {\n this._teardownScheduled = false;\n this._teardownNow();\n this.connectedCallback();\n }\n\n /**\n * Set a prop value. Called by the factory and by direct property assignment.\n * The Proxy intercepts and creates/updates backing signals.\n *\n * Precedence = DEFINED-write-pins (ADR 0025 item 3): a defined\n * value pins the key so a declared attribute no longer overwrites it. An\n * explicit `undefined` (e.g. a factory spreading an unset optional prop \u2014\n * `{...opts}` fires _setProp(key, undefined) per the Object.entries walk)\n * must NOT pin; it falls back to the declared attribute/default, matching\n * attr()'s coalesce. For a declared attribute, that fallback re-resolves\n * from the current attribute, keeping declared-default booleans\n * non-undefined.\n */\n _setProp(key: string, value: unknown): void {\n // Unknown-prop echo (N202, dev, ADR 0030.2): post-mount, a DEFINED write\n // to a key setup() never read is attribution noise \u2014 a misspelled prop\n // (`varian=`) arriving through the factory or the template\n // auto-resolution path (both funnel through _setProp). `undefined`\n // writes are exempt BY CONTRACT: a factory spreading unset optional\n // props fires _setProp(key, undefined) per the Object.entries walk (see\n // the pinning doc above). Attribution-only \u2014 the write still lands\n // exactly as before; deduped per key per element.\n if (\n process.env.NODE_ENV !== 'production'\n && completedSetups!.has(this)\n && value !== undefined\n && isDev()\n // A declared attrs key is contract, not a typo \u2014 'forward' especially,\n // where CORE owns the read and setup() never touches the prop. Gate\n // ruling on ADR 0030.2 \u00A78 T6 Q3: declared keys are exempt.\n && !attrEntriesByKey.has(key)\n && !propsReadKeys!.get(this._propsProxy!.props)!.has(key)\n && !echoedProps!.get(this)?.has(key)\n ) {\n const echoed = echoedProps!.get(this) ?? new Set<string>();\n echoed.add(key);\n echoedProps!.set(this, echoed);\n diag(\n 'N202',\n `<${tagName}> prop '${key}' was set but setup() never read it \u2014 misspelled or unsupported prop?`,\n { tag: tagName, key },\n );\n }\n if (value === undefined) {\n this._pinned.delete(key);\n const entry = attrEntriesByKey.get(key);\n if (entry) {\n const raw = this.getAttribute(entry.attrName);\n // A 'forward' attribute is removed from the host on adoption, so an\n // unpin can't re-read it \u2014 resolve the absent value directly to CLEAR\n // a previously pinned id/name (rev audit fix). _applyAttr's forward\n // branch deliberately no-ops on absent (so the seed/attr callback\n // never clobber an adopted value); the explicit unpin does not.\n if (raw === null && declType(entry.decl) === 'forward') {\n this._propsProxy?.setProperty(key, resolveAttrValue(entry.decl, null));\n } else {\n this._applyAttr(entry, raw);\n }\n return;\n }\n } else {\n this._pinned.add(key);\n }\n this._propsProxy?.setProperty(key, value);\n }\n\n /**\n * Read a prop's backing signal (framework-internal). Used by children()\n * to fold the factory `children` prop into the projection slot (ADR 0025\n * item 1) without a new public read API.\n */\n _propSignal(key: string): Signal<unknown> {\n return (this._propsProxy!.props as Record<string, Signal<unknown>>)[key]!;\n }\n\n /**\n * Whether a prop is currently pinned by a defined factory/property write\n * (framework-internal, like _propSignal). The attribute-as-truth open-state\n * pattern needs this as its controlled-mode discriminator: a declared\n * 'boolean' prop is never undefined (absent \u2192 false), so pin state is the\n * ONLY way a component can tell \"parent drives this\" from \"the attribute\n * drives this\" \u2014 e.g. setOpen() must not write the open attribute while a\n * controlled parent has it pinned, or the DOM attribute diverges from the\n * pinned state until the next prop write.\n */\n _isPinned(key: string): boolean {\n return this._pinned.has(key);\n }\n }\n\n // Register the custom element. A DUPLICATE definition is a coded dev error\n // (N201, ADR 0030.2 \u00A73/\u00A78): first-wins-silently rendered the WRONG component\n // while the second factory typechecked against the wrong props. The error\n // names both definition sites and is thrown to the SECOND definer; the\n // first definition stays live (first-wins preserved). Exemptions:\n // - HMR re-evaluations arrive with the registry's marked indirection thunk\n // (hmr/registry.ts __register) \u2014 the live thunk already swapped setup, so\n // re-registration is the mechanism working, not a collision.\n // - Production keeps the historical SILENT first-wins: a name clash must\n // not take down a live page (redefine-as-swap stays HMR/dev-only because\n // the copy-in registry model makes genuine duplicate tags structural).\n if (!customElements.get(tagName)) {\n customElements.define(tagName, FrameworkComponent);\n if (process.env.NODE_ENV !== 'production' && isDev()) firstDefineSites!.set(tagName, captureDefineSite());\n } else if (process.env.NODE_ENV !== 'production' && isDev() && !(setup as { __nisliHmr?: boolean }).__nisliHmr) {\n const first = firstDefineSites!.get(tagName)\n ?? '(unknown site \u2014 defined outside component(), or before dev mode was enabled)';\n throw new Error(formatDiag(\n 'N201',\n `duplicate define for <${tagName}>. First defined ${first}; the second `\n + `definition site is this error's own stack. The first definition stays `\n + `live (first-wins) \u2014 rename one of the components.`,\n ));\n }\n\n // Return the typed factory function\n const factory: ComponentFactory<P> = (props, hostAttrs?) => {\n return {\n __type: 'factory' as const,\n tagName,\n props: props as Record<string, PropInput<unknown>>,\n hostAttrs,\n } as unknown as TemplateResult;\n };\n\n return factory;\n}\n\n/**\n * Ensure a value is a signal. If it already is, return as-is.\n * If it's a plain value, wrap it in signal().\n * Used by factory mount to support PropInput<T> = T | Signal<T>.\n */\nfunction ensureSignal<T>(value: PropInput<T>): ReadonlySignal<T> {\n if (isSignal(value)) return value as ReadonlySignal<T>;\n return signal(value as T);\n}\n\n/**\n * Mount a TemplateResult into a host element.\n * This is the bridge between component.ts and template.ts.\n * Template.ts will provide the actual implementation via the\n * TemplateResult.mount() method.\n *\n * @param hostImpl \u2014 optional ComponentHostImpl for registering disposers\n */\nfunction mountTemplate(host: HTMLElement, result: TemplateResult, hostImpl?: ComponentHostImpl): void {\n if (result && typeof result === 'object' && 'mount' in result && typeof result.mount === 'function') {\n result.mount(host);\n } else if (result && typeof result === 'object' && '__type' in result) {\n // Factory result \u2014 create the child element\n const factoryResult = result as unknown as {\n __type: 'factory';\n tagName: string;\n props: Record<string, PropInput<unknown>>;\n hostAttrs?: HostAttrs;\n };\n const el = document.createElement(factoryResult.tagName);\n\n // Forward props as signals (auto-wrap plain values)\n for (const [key, raw] of Object.entries(factoryResult.props)) {\n const sig = ensureSignal(raw);\n (el as any)._setProp(key, sig.value);\n // Subscribe to signal changes to update the child's props\n const unsub = sig.subscribe((newVal: unknown) => {\n (el as any)._setProp(key, newVal);\n });\n // Register unsub for cleanup on disconnect\n if (hostImpl) {\n hostImpl.addDisposer(unsub);\n }\n }\n\n // Apply host-level attributes (class)\n if (factoryResult.hostAttrs?.class != null) {\n const classSig = ensureSignal(factoryResult.hostAttrs.class);\n // Apply initial value\n applyHostClass(el, '', classSig.value);\n // Subscribe to reactive changes\n let prevClass = classSig.value;\n const unsub = classSig.subscribe((newClass: string) => {\n applyHostClass(el, prevClass, newClass);\n prevClass = newClass;\n });\n if (hostImpl) {\n hostImpl.addDisposer(unsub);\n }\n }\n\n host.appendChild(el);\n }\n}\n\n/**\n * Apply a class string to an element via classList, replacing the previous\n * class string's classes. Uses add/remove to avoid interfering with classes\n * set by the component's own template (class:name directives, etc.).\n */\nfunction applyHostClass(el: Element, prev: string, next: string): void {\n const prevClasses = prev ? prev.split(/\\s+/).filter(Boolean) : [];\n const nextClasses = next ? next.split(/\\s+/).filter(Boolean) : [];\n for (const cls of prevClasses) {\n if (!nextClasses.includes(cls)) el.classList.remove(cls);\n }\n for (const cls of nextClasses) {\n if (!prevClasses.includes(cls)) el.classList.add(cls);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
let currentComponent = null;
|
|
2
|
+
function runWithContext(component, fn) {
|
|
3
|
+
const prev = currentComponent;
|
|
4
|
+
currentComponent = component;
|
|
5
|
+
try {
|
|
6
|
+
fn();
|
|
7
|
+
} finally {
|
|
8
|
+
currentComponent = prev;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function getCurrentComponent() {
|
|
12
|
+
if (!currentComponent) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"getCurrentComponent() called outside setup(). inject(), effect(), and other context-dependent functions can only be called during component initialization."
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return currentComponent;
|
|
18
|
+
}
|
|
19
|
+
function hasContext() {
|
|
20
|
+
return currentComponent !== null;
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
getCurrentComponent,
|
|
24
|
+
hasContext,
|
|
25
|
+
runWithContext
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/context.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * context.ts \u2014 Setup context for pure function DI\n *\n * The setup context lets pure functions (inject(), listen(), effect())\n * access the component they belong to without needing `this`.\n *\n * The context exists ONLY during component initialization (setup()).\n * It is NOT a runtime thing \u2014 it's a synchronous initialization-time concept.\n *\n * Same pattern as Angular's inject(), Solid's createSignal(), Vue's setup().\n */\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Minimal interface for a component host. component.ts provides the\n * full implementation; this file only needs the disposal hook.\n */\nexport interface ComponentHost {\n /** Register a cleanup function to run on disconnectedCallback */\n addDisposer(fn: () => void): void;\n /** The raw HTMLElement */\n element: HTMLElement;\n}\n\n// \u2500\u2500 Context state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nlet currentComponent: ComponentHost | null = null;\n\n// \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Run a function with a component as the active context.\n * Pure functions called during `fn` can access the component via\n * `getCurrentComponent()`.\n *\n * Context is strictly synchronous \u2014 it does NOT survive across\n * await boundaries or microtasks.\n */\nexport function runWithContext(component: ComponentHost, fn: () => void): void {\n const prev = currentComponent;\n currentComponent = component;\n try {\n fn();\n } finally {\n currentComponent = prev;\n }\n}\n\n/**\n * Get the currently active component host.\n * Throws if called outside a setup() context.\n *\n * This is framework-internal \u2014 component authors use inject(), effect(),\n * etc., which call this internally.\n */\nexport function getCurrentComponent(): ComponentHost {\n if (!currentComponent) {\n throw new Error(\n 'getCurrentComponent() called outside setup(). ' +\n 'inject(), effect(), and other context-dependent functions ' +\n 'can only be called during component initialization.'\n );\n }\n return currentComponent;\n}\n\n/**\n * Check if there is an active setup context.\n * Returns true during component setup, false otherwise.\n */\nexport function hasContext(): boolean {\n return currentComponent !== null;\n}\n"],
|
|
5
|
+
"mappings": "AA2BA,IAAI,mBAAyC;AAYtC,SAAS,eAAe,WAA0B,IAAsB;AAC7E,QAAM,OAAO;AACb,qBAAmB;AACnB,MAAI;AACF,OAAG;AAAA,EACL,UAAE;AACA,uBAAmB;AAAA,EACrB;AACF;AASO,SAAS,sBAAqC;AACnD,MAAI,CAAC,kBAAkB;AACrB,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AACA,SAAO;AACT;AAMO,SAAS,aAAsB;AACpC,SAAO,qBAAqB;AAC9B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|