what-core 0.12.4 → 0.13.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/dist/chunk-ENARGHSD.min.js +11 -0
- package/dist/chunk-OKM3GKVP.min.js +1 -0
- package/dist/index.min.js +82 -5
- package/dist/render.min.js +1 -1
- package/dist/testing.min.js +1 -1
- package/index.d.ts +40 -3
- package/package.json +1 -1
- package/render.d.ts +7 -0
- package/src/a11y.js +3 -3
- package/src/agent-context.js +1 -1
- package/src/animation.js +5 -6
- package/src/components.js +1 -1
- package/src/data.js +1 -10
- package/src/dom.js +29 -19
- package/src/errors.js +321 -0
- package/src/form.js +2 -1
- package/src/head.js +2 -1
- package/src/hooks.js +10 -17
- package/src/index.js +1 -0
- package/src/reactive.js +31 -14
- package/src/render.js +11 -14
- package/src/scheduler.js +7 -7
- package/src/skeleton.js +16 -1
- package/src/store.js +0 -1
- package/src/testing.js +101 -50
- package/testing.d.ts +17 -1
- package/dist/chunk-M5GDJRVX.min.js +0 -1
- package/dist/chunk-T2SKNKT5.min.js +0 -11
package/dist/testing.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{W as x,a as k,b as _,c as E,d as b,f as y,i as h,m as g}from"./chunk-OKM3GKVP.min.js";import{a as v}from"./chunk-O3SKPRTY.min.js";var f=null;function N(){return typeof document<"u"&&(f=document.createElement("div"),f.id="test-root",document.body.appendChild(f)),f}function q(){f&&(f.innerHTML="",f.parentNode&&f.parentNode.removeChild(f),f=null)}function H(e,t={}){let{container:n}=t,o=n||N();if(!o)throw new Error("No DOM container available. Are you running in Node.js without jsdom?");let s=x(e,o);return{container:o,unmount:s,getByText:r=>p(o,r),getByTestId:r=>o.querySelector(`[data-testid="${r}"]`),getByRole:r=>o.querySelector(`[role="${r}"]`),getAllByText:r=>$(o,r),queryByText:r=>p(o,r),queryByTestId:r=>o.querySelector(`[data-testid="${r}"]`),debug:()=>console.log(o.innerHTML),findByText:(r,u)=>S(()=>p(o,r),{timeout:u}),findByTestId:(r,u)=>S(()=>o.querySelector(`[data-testid="${r}"]`),{timeout:u})}}function O(e,t){let n=N();if(!n)throw new Error("No DOM container available. Are you running in Node.js without jsdom?");let o={},s=null,r;return g(u=>{s=u;let c=v(e,t||{});r=x(c,n)}),{container:n,signals:new Proxy(o,{get(u,c){if(c in u)return u[c]},set(u,c,i){return u[c]=i,!0}}),update(){h()},unmount(){r&&r(),s&&s(),q()},getByText:u=>p(n,u),getByTestId:u=>n.querySelector(`[data-testid="${u}"]`),queryByText:u=>p(n,u),debug:()=>console.log(n.innerHTML)}}function R(){h()}var B="(unnamed)";function F(e){if(!k)throw new Error('[what] trackSignals() requires a development build. Signal debug names and subscriber back-references are stripped in production, so there is nothing to report. Run your tests with NODE_ENV !== "production".');let t=[],n=[],o=(l,d)=>{l.includes(d)||l.push(d)},s=_,r={...s||{},onSignalUpdate(l){o(n,l?._debugName||B),s?.onSignalUpdate?.(l)}};delete r.__isPreinstallBuffer;let u=b(0,"__trackSignals_probe__"),c={current:null},i=null,a=(l,d)=>{for(let w of l||[]){if(d.has(w))continue;d.add(w);let m=w._signalOwner;if(m){m!==u&&o(t,m._debugName||B);continue}let T=w._owner;T?.deps&&a(T.deps,d)}};E(r);try{g(d=>{c.current=d,y(()=>{u(),e()})});let l=new Set;for(let d of u._subs)a(d.deps,l)}catch(l){i=l}finally{c.current?.(),E(s)}if(i)throw i;return{accessed:t,written:n}}function A(e,t){let n=[t],o=0,s=b(t,e),r=s.set;s.set=function(i){let a=typeof i=="function"?i(s.peek()):i;return Object.is(s.peek(),a)||(o++,n.push(a)),r(a)};let u=s,c=function(...i){if(i.length===0)return u();let a=typeof i[0]=="function"?i[0](u.peek()):i[0];return Object.is(u.peek(),a)||(o++,n.push(a)),u(a)};return c._signal=!0,c.peek=s.peek,c.set=s.set,c.subscribe=s.subscribe,s._debugName&&(c._debugName=s._debugName),s._subs&&(c._subs=s._subs),Object.defineProperty(c,"history",{get(){return n}}),Object.defineProperty(c,"setCount",{get(){return o}}),c.reset=function(i){let a=i!==void 0?i:t;n.length=0,n.push(a),o=0,u(a)},c}function p(e,t){let n=t instanceof RegExp?t:null,o=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,null);for(;o.nextNode();){let s=o.currentNode,r=s.textContent||"";if(n?n.test(r):r.includes(t))return s.parentElement}return null}function $(e,t){let n=[],o=t instanceof RegExp?t:null,s=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,null);for(;s.nextNode();){let r=s.currentNode,u=r.textContent||"";(o?o.test(u):u.includes(t))&&n.push(r.parentElement)}return n}var P={click(e){let t=new MouseEvent("click",{bubbles:!0,cancelable:!0,view:typeof window<"u"?window:void 0});return e.dispatchEvent(t),t},change(e,t){e.value=t;let n=new Event("input",{bubbles:!0});e.dispatchEvent(n);let o=new Event("change",{bubbles:!0});return e.dispatchEvent(o),o},input(e,t){e.value=t;let n=new Event("input",{bubbles:!0});return e.dispatchEvent(n),n},submit(e){let t=new Event("submit",{bubbles:!0,cancelable:!0});return e.dispatchEvent(t),t},focus(e){e.focus();let t=new FocusEvent("focus",{bubbles:!0});return e.dispatchEvent(t),t},blur(e){e.blur();let t=new FocusEvent("blur",{bubbles:!0});return e.dispatchEvent(t),t},keyDown(e,t,n={}){let o=new KeyboardEvent("keydown",{bubbles:!0,cancelable:!0,key:t,...n});return e.dispatchEvent(o),o},keyUp(e,t,n={}){let o=new KeyboardEvent("keyup",{bubbles:!0,cancelable:!0,key:t,...n});return e.dispatchEvent(o),o},mouseEnter(e){let t=new MouseEvent("mouseenter",{bubbles:!0});return e.dispatchEvent(t),t},mouseLeave(e){let t=new MouseEvent("mouseleave",{bubbles:!0});return e.dispatchEvent(t),t}};async function S(e,t={}){let{timeout:n=1e3,interval:o=50}=t,s=Date.now();for(;Date.now()-s<n;){try{let r=e();if(r)return r}catch{}await new Promise(r=>setTimeout(r,o))}throw new Error(`waitFor timed out after ${n}ms`)}async function j(e,t={}){let{timeout:n=1e3,interval:o=50}=t,s=Date.now(),r=e();if(!r)throw new Error("Element not found");for(;Date.now()-s<n;){if(r=e(),!r)return;await new Promise(u=>setTimeout(u,o))}throw new Error(`Element still present after ${n}ms`)}async function I(e){let t=await e();return h(),await new Promise(n=>queueMicrotask(()=>n())),await new Promise(n=>setTimeout(()=>n(),0)),t}function L(e){let t=b(e),n=[e];return y(()=>{n.push(t())}),{signal:t,get value(){return t()},set value(o){t.set(o)},history:n,reset(){n.length=0,n.push(t())}}}function V(e="MockComponent"){let t=[];function n(o){return t.push({props:o,timestamp:Date.now()}),v("div",{"data-testid":`mock-${e}`},JSON.stringify(o,null,2))}return n.displayName=e,n.calls=t,n.lastCall=()=>t[t.length-1],n.reset=()=>{t.length=0},n}var U={toBeInTheDocument(e){if(!e||!e.parentNode)throw new Error("Expected element to be in the document")},toHaveTextContent(e,t){if(!e)throw new Error("Element not found");let n=e.textContent;if(!(t instanceof RegExp?t.test(n):n.includes(t)))throw new Error(`Expected "${n}" to contain "${t}"`)},toHaveAttribute(e,t,n){if(!e)throw new Error("Element not found");let o=e.getAttribute(t);if(n!==void 0&&o!==n)throw new Error(`Expected attribute "${t}" to be "${n}", got "${o}"`);if(n===void 0&&o===null)throw new Error(`Expected element to have attribute "${t}"`)},toHaveClass(e,t){if(!e)throw new Error("Element not found");if(!e.classList.contains(t))throw new Error(`Expected element to have class "${t}"`)},toBeVisible(e){if(!e)throw new Error("Element not found");let t=window.getComputedStyle(e);if(t.display==="none"||t.visibility==="hidden"||t.opacity==="0")throw new Error("Expected element to be visible")},toBeDisabled(e){if(!e)throw new Error("Element not found");if(!e.disabled)throw new Error("Expected element to be disabled")},toHaveValue(e,t){if(!e)throw new Error("Element not found");if(e.value!==t)throw new Error(`Expected value to be "${t}", got "${e.value}"`)}},W={getByText:e=>p(document.body,e),getByTestId:e=>document.querySelector(`[data-testid="${e}"]`),getByRole:e=>document.querySelector(`[role="${e}"]`),getAllByText:e=>$(document.body,e),queryByText:e=>p(document.body,e),queryByTestId:e=>document.querySelector(`[data-testid="${e}"]`),debug:()=>console.log(document.body.innerHTML)};export{I as act,q as cleanup,L as createTestSignal,U as expect,P as fireEvent,R as flushEffects,V as mockComponent,A as mockSignal,H as render,O as renderTest,W as screen,N as setupDOM,F as trackSignals,S as waitFor,j as waitForElementToBeRemoved};
|
package/index.d.ts
CHANGED
|
@@ -52,10 +52,17 @@ export function onRootCleanup(fn: () => void): void;
|
|
|
52
52
|
// --- Virtual DOM ---
|
|
53
53
|
|
|
54
54
|
export type PrimitiveChild = string | number | boolean | null | undefined;
|
|
55
|
-
|
|
55
|
+
|
|
56
|
+
// `VNode<any>`, not `VNode`. VNode is invariant in P (its `tag` holds a
|
|
57
|
+
// `Component<P>`, whose parameter position is contravariant under
|
|
58
|
+
// strictFunctionTypes), so `VNode` — which means `VNode<Record<string, any>>` —
|
|
59
|
+
// rejects every specifically-typed node. `h('div', {}, h('h1', { style: '' }))`
|
|
60
|
+
// did not compile for any TypeScript user before 0.12.5, and neither did
|
|
61
|
+
// passing that tree to mount().
|
|
62
|
+
export type VNodeChild = PrimitiveChild | VNode<any> | (() => VNodeChild) | VNodeChild[];
|
|
56
63
|
|
|
57
64
|
/** A component may legitimately render nothing, so `null` is part of the contract. */
|
|
58
|
-
export type Component<P = {}> = ((props: P & { children?: VNodeChild }) => VNode | null) & {
|
|
65
|
+
export type Component<P = {}> = ((props: P & { children?: VNodeChild }) => VNode<any> | null) & {
|
|
59
66
|
/**
|
|
60
67
|
* Opt out of realizing compiled children before the component runs.
|
|
61
68
|
*
|
|
@@ -89,7 +96,7 @@ export function html(strings: TemplateStringsArray, ...values: any[]): VNode | V
|
|
|
89
96
|
|
|
90
97
|
// --- DOM ---
|
|
91
98
|
|
|
92
|
-
export function mount(vnode: VNodeChild, container: string | Element): () => void;
|
|
99
|
+
export function mount(vnode: VNodeChild, container: string | Element | DocumentFragment): () => void;
|
|
93
100
|
|
|
94
101
|
/** Attach reactive bindings to server-rendered DOM instead of creating it. */
|
|
95
102
|
export function hydrate(vnode: VNodeChild, container: Element): Node | null;
|
|
@@ -867,6 +874,15 @@ export interface ErrorCodeDefinition {
|
|
|
867
874
|
|
|
868
875
|
export const ERROR_CODES: Record<string, ErrorCodeDefinition>;
|
|
869
876
|
|
|
877
|
+
/**
|
|
878
|
+
* Look up a catalogue entry by its `ERR_*` code.
|
|
879
|
+
*
|
|
880
|
+
* Errors thrown outside what-core carry only their code — the suggestion and
|
|
881
|
+
* the worked example live once, in the catalogue, so that the client bundle
|
|
882
|
+
* does not have to ship the prose. This is how they are recovered.
|
|
883
|
+
*/
|
|
884
|
+
export function getErrorDefinition(code: string): ErrorCodeDefinition | undefined;
|
|
885
|
+
|
|
870
886
|
export interface WhatErrorJSON {
|
|
871
887
|
code: string;
|
|
872
888
|
message: string;
|
|
@@ -965,3 +981,24 @@ export function getMountedComponents(): unknown[];
|
|
|
965
981
|
export function registerSignal(sig: unknown): void;
|
|
966
982
|
export function unregisterSignal(sig: unknown): void;
|
|
967
983
|
export function getActiveSignals(): unknown[];
|
|
984
|
+
|
|
985
|
+
// --- Internal cross-package exports ---
|
|
986
|
+
// Underscore-prefixed names are not public API and carry no compatibility
|
|
987
|
+
// promise. They are declared here because sibling packages in this repo import
|
|
988
|
+
// them across the package boundary (what-server, what-text), and an
|
|
989
|
+
// undeclared cross-package import is exactly the rename that ships broken:
|
|
990
|
+
// `hygiene:types` skips `_`-prefixed names in its reverse direction, so
|
|
991
|
+
// nothing else would have caught it. Application code should not use these.
|
|
992
|
+
|
|
993
|
+
/** @internal Used by what-server. True for `aria-*` and `data-*` attribute names. */
|
|
994
|
+
export function _isAriaAttr(name: string): boolean;
|
|
995
|
+
/** @internal Used by what-server. Opens an SSR component scope. */
|
|
996
|
+
export function _beginComponentSSR(...args: unknown[]): unknown;
|
|
997
|
+
/** @internal Used by what-server. Closes the scope opened by `_beginComponentSSR`. */
|
|
998
|
+
export function _endComponentSSR(...args: unknown[]): unknown;
|
|
999
|
+
/** @internal Used by what-server. The keyed-array mapping `<For>` compiles down to. */
|
|
1000
|
+
export function _mapArrayToArray(...args: unknown[]): unknown;
|
|
1001
|
+
/** @internal Used by what-server/node. Installs the Node AsyncLocalStorage backend. */
|
|
1002
|
+
export function __installServerContextStorage(storage: unknown): void;
|
|
1003
|
+
/** @internal Used by what-text. Lets the text engine intercept text-node insertion. */
|
|
1004
|
+
export function _setTextInsertHook(hook: unknown): void;
|
package/package.json
CHANGED
package/render.d.ts
CHANGED
|
@@ -42,3 +42,10 @@ export function isHydrating(): boolean;
|
|
|
42
42
|
// SVG counterpart to template(): elements are created in the SVG namespace, which
|
|
43
43
|
// a plain innerHTML template cannot do.
|
|
44
44
|
export function svgTemplate(html: string): () => Element;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @internal The component-call helper the compiler emits for `<Component />`.
|
|
48
|
+
* Re-exported by what-framework/render so compiled output can import it from
|
|
49
|
+
* the package a scaffolded app actually depends on.
|
|
50
|
+
*/
|
|
51
|
+
export function _$createComponent(component: unknown, props?: unknown, ...args: unknown[]): unknown;
|
package/src/a11y.js
CHANGED
|
@@ -21,7 +21,7 @@ export function useFocus() {
|
|
|
21
21
|
return {
|
|
22
22
|
current: () => focusedElement(),
|
|
23
23
|
focus: (element) => element?.focus(),
|
|
24
|
-
blur: () => document.activeElement?.blur(),
|
|
24
|
+
blur: () => /** @type {HTMLElement | null} */ (document.activeElement)?.blur(),
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -241,7 +241,7 @@ export function SkipLink({ href = '#main', children = 'Skip to content' }) {
|
|
|
241
241
|
class: 'what-skip-link',
|
|
242
242
|
onClick: (e) => {
|
|
243
243
|
e.preventDefault();
|
|
244
|
-
const target = document.querySelector(href);
|
|
244
|
+
const target = /** @type {HTMLElement | null} */ (document.querySelector(href));
|
|
245
245
|
if (target) {
|
|
246
246
|
target.focus();
|
|
247
247
|
target.scrollIntoView();
|
|
@@ -683,7 +683,7 @@ export function useDescribedBy(description) {
|
|
|
683
683
|
|
|
684
684
|
// --- Labelledby ---
|
|
685
685
|
|
|
686
|
-
export function useLabelledBy(
|
|
686
|
+
export function useLabelledBy(_label) {
|
|
687
687
|
const id = useId('label');
|
|
688
688
|
|
|
689
689
|
return {
|
package/src/agent-context.js
CHANGED
|
@@ -8,7 +8,7 @@ import { getCollectedErrors } from './errors.js';
|
|
|
8
8
|
// --- Version ---
|
|
9
9
|
// Keep in sync with packages/core/package.json (checked by
|
|
10
10
|
// core/test/guardrails.test.js so it can't silently go stale again).
|
|
11
|
-
const VERSION = '0.
|
|
11
|
+
const VERSION = '0.13.0';
|
|
12
12
|
|
|
13
13
|
// --- Component Registry ---
|
|
14
14
|
// Tracks mounted components for agent inspection.
|
package/src/animation.js
CHANGED
|
@@ -213,7 +213,7 @@ export function useTransition(options = {}) {
|
|
|
213
213
|
isTransitioning.set(true);
|
|
214
214
|
progress.set(0);
|
|
215
215
|
|
|
216
|
-
return new Promise((resolve) => {
|
|
216
|
+
return /** @type {Promise<void>} */ (new Promise((resolve) => {
|
|
217
217
|
const startTime = performance.now();
|
|
218
218
|
|
|
219
219
|
function tick(time) {
|
|
@@ -231,7 +231,7 @@ export function useTransition(options = {}) {
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
requestAnimationFrame(tick);
|
|
234
|
-
});
|
|
234
|
+
}));
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
return {
|
|
@@ -339,7 +339,7 @@ export function useGesture(element, handlers = {}) {
|
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
function handleEnd(
|
|
342
|
+
function handleEnd(_e) {
|
|
343
343
|
if (!state.isDragging.peek()) return;
|
|
344
344
|
|
|
345
345
|
if (longPressTimer) {
|
|
@@ -453,7 +453,6 @@ export function useGesture(element, handlers = {}) {
|
|
|
453
453
|
|
|
454
454
|
export function useAnimatedValue(initialValue) {
|
|
455
455
|
const value = signal(initialValue);
|
|
456
|
-
const animations = [];
|
|
457
456
|
|
|
458
457
|
return {
|
|
459
458
|
value: () => value(),
|
|
@@ -531,7 +530,7 @@ export function createTransitionClasses(name) {
|
|
|
531
530
|
export async function cssTransition(element, name, type = 'enter', duration = 300) {
|
|
532
531
|
const classes = createTransitionClasses(name);
|
|
533
532
|
|
|
534
|
-
return new Promise((resolve) => {
|
|
533
|
+
return /** @type {Promise<void>} */ (new Promise((resolve) => {
|
|
535
534
|
scheduleWrite(() => {
|
|
536
535
|
// Initial state
|
|
537
536
|
element.classList.add(classes[type]);
|
|
@@ -554,5 +553,5 @@ export async function cssTransition(element, name, type = 'enter', duration = 30
|
|
|
554
553
|
});
|
|
555
554
|
});
|
|
556
555
|
});
|
|
557
|
-
});
|
|
556
|
+
}));
|
|
558
557
|
}
|
package/src/components.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// memo, lazy, Suspense, ErrorBoundary
|
|
3
3
|
|
|
4
4
|
import { h } from './h.js';
|
|
5
|
-
import { signal,
|
|
5
|
+
import { signal, __DEV__ } from './reactive.js';
|
|
6
6
|
import { isServerRender } from './server-context.js';
|
|
7
7
|
|
|
8
8
|
// Legacy errorBoundaryStack removed — tree-based resolution via _parentCtx._errorBoundary
|
package/src/data.js
CHANGED
|
@@ -288,16 +288,7 @@ export function useFetch(url, options = {}) {
|
|
|
288
288
|
// Stale-while-revalidate pattern with caching
|
|
289
289
|
|
|
290
290
|
export function useSWR(rawKey, fetcher, options = {}) {
|
|
291
|
-
const {
|
|
292
|
-
revalidateOnFocus = true,
|
|
293
|
-
revalidateOnReconnect = true,
|
|
294
|
-
refreshInterval = 0,
|
|
295
|
-
dedupingInterval = 2000,
|
|
296
|
-
fallbackData,
|
|
297
|
-
onSuccess,
|
|
298
|
-
onError,
|
|
299
|
-
suspense = false,
|
|
300
|
-
} = options;
|
|
291
|
+
const { revalidateOnFocus = true, revalidateOnReconnect = true, refreshInterval = 0, dedupingInterval = 2000, fallbackData, onSuccess, onError } = options;
|
|
301
292
|
|
|
302
293
|
// Support null/undefined/false key for conditional/dependent fetching
|
|
303
294
|
// When key is falsy, don't fetch — return idle state
|
package/src/dom.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Components run ONCE. Signals create individual DOM effects.
|
|
3
3
|
// No VDOM reconciler, no diffing — direct DOM manipulation driven by signals.
|
|
4
4
|
|
|
5
|
-
import { effect,
|
|
6
|
-
import { reportError, _injectGetCurrentComponent
|
|
5
|
+
import { effect, untrack, signal, __DEV__, __devtools } from './reactive.js';
|
|
6
|
+
import { reportError, _injectGetCurrentComponent } from './components.js';
|
|
7
7
|
import { _setComponentRef } from './helpers.js';
|
|
8
8
|
// SVG elements that need namespace
|
|
9
9
|
const SVG_ELEMENTS = new Set([
|
|
@@ -119,7 +119,7 @@ function disposeComponent(ctx) {
|
|
|
119
119
|
// Run effect disposals
|
|
120
120
|
if (ctx.effects) {
|
|
121
121
|
for (const dispose of ctx.effects) {
|
|
122
|
-
try { dispose(); } catch
|
|
122
|
+
try { dispose(); } catch { /* already disposed */ }
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -139,7 +139,7 @@ function disposeComponent(ctx) {
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
if (__DEV__ && __devtools?.onComponentUnmount) __devtools.onComponentUnmount(ctx);
|
|
142
|
+
if (__DEV__ && __devtools?.onComponentUnmount) __devtools.onComponentUnmount?.(ctx);
|
|
143
143
|
mountedComponents.delete(ctx);
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -169,7 +169,7 @@ export function disposeTree(node) {
|
|
|
169
169
|
const disposers = node._hydrationDisposers;
|
|
170
170
|
node._hydrationDisposers = null;
|
|
171
171
|
for (let i = 0; i < disposers.length; i++) {
|
|
172
|
-
try { disposers[i](); } catch
|
|
172
|
+
try { disposers[i](); } catch { /* already disposed */ }
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
// Check comment node WeakMap for component context — only for comment nodes
|
|
@@ -181,12 +181,12 @@ export function disposeTree(node) {
|
|
|
181
181
|
}
|
|
182
182
|
// Dispose reactive function child effects ({() => ...} wrappers)
|
|
183
183
|
if (node._dispose) {
|
|
184
|
-
try { node._dispose(); } catch
|
|
184
|
+
try { node._dispose(); } catch { /* already disposed */ }
|
|
185
185
|
}
|
|
186
186
|
// Dispose reactive prop effects (value: () => ..., class: () => ..., etc.)
|
|
187
187
|
if (node._propEffects) {
|
|
188
188
|
for (const key in node._propEffects) {
|
|
189
|
-
try { node._propEffects[key](); } catch
|
|
189
|
+
try { node._propEffects[key](); } catch { /* already disposed */ }
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
// Recursively dispose children
|
|
@@ -384,7 +384,7 @@ const _propsProxyHandler = {
|
|
|
384
384
|
}
|
|
385
385
|
return undefined;
|
|
386
386
|
},
|
|
387
|
-
set(
|
|
387
|
+
set(_target, _key) {
|
|
388
388
|
// Props are read-only from the component's perspective.
|
|
389
389
|
// Reject all writes — especially dangerous prototype-chain keys.
|
|
390
390
|
return false;
|
|
@@ -436,6 +436,7 @@ export function _beginComponentSSR(Component) {
|
|
|
436
436
|
const ctx = {
|
|
437
437
|
hooks: [],
|
|
438
438
|
hookIndex: 0,
|
|
439
|
+
/** @type {Array<() => void>} */
|
|
439
440
|
effects: [],
|
|
440
441
|
cleanups: [],
|
|
441
442
|
mounted: false,
|
|
@@ -563,6 +564,7 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
563
564
|
const ctx = {
|
|
564
565
|
hooks: [],
|
|
565
566
|
hookIndex: 0,
|
|
567
|
+
/** @type {Array<() => void>} */
|
|
566
568
|
effects: [],
|
|
567
569
|
cleanups: [],
|
|
568
570
|
mounted: false,
|
|
@@ -587,7 +589,7 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
587
589
|
|
|
588
590
|
// Track for disposal
|
|
589
591
|
mountedComponents.add(ctx);
|
|
590
|
-
if (__DEV__ && __devtools?.onComponentMount) __devtools.onComponentMount(ctx);
|
|
592
|
+
if (__DEV__ && __devtools?.onComponentMount) __devtools.onComponentMount?.(ctx);
|
|
591
593
|
|
|
592
594
|
// Props signal for reactive updates from parent
|
|
593
595
|
const propsChildren = children.length === 0 ? undefined : children.length === 1 ? children[0] : children;
|
|
@@ -699,7 +701,9 @@ function createErrorBoundary(vnode, parent) {
|
|
|
699
701
|
const endComment = document.createComment('eb:end');
|
|
700
702
|
|
|
701
703
|
const boundaryCtx = {
|
|
702
|
-
hooks: []
|
|
704
|
+
hooks: /** @type {any[]} */ ([]), hookIndex: 0,
|
|
705
|
+
effects: /** @type {Array<() => void>} */ ([]),
|
|
706
|
+
cleanups: /** @type {Array<() => void>} */ ([]),
|
|
703
707
|
mounted: false, disposed: false,
|
|
704
708
|
_parentCtx: componentStack[componentStack.length - 1] || null,
|
|
705
709
|
_errorBoundary: handleError,
|
|
@@ -719,11 +723,12 @@ function createErrorBoundary(vnode, parent) {
|
|
|
719
723
|
componentStack.push(boundaryCtx);
|
|
720
724
|
|
|
721
725
|
// Remove old content between comment boundaries
|
|
722
|
-
|
|
726
|
+
const openParent = startComment.parentNode;
|
|
727
|
+
if (openParent) {
|
|
723
728
|
while (startComment.nextSibling && startComment.nextSibling !== endComment) {
|
|
724
729
|
const old = startComment.nextSibling;
|
|
725
730
|
disposeTree(old);
|
|
726
|
-
|
|
731
|
+
openParent.removeChild(old);
|
|
727
732
|
}
|
|
728
733
|
}
|
|
729
734
|
|
|
@@ -767,7 +772,9 @@ function createSuspenseBoundary(vnode, parent) {
|
|
|
767
772
|
const endComment = document.createComment('sb:end');
|
|
768
773
|
|
|
769
774
|
const boundaryCtx = {
|
|
770
|
-
hooks: []
|
|
775
|
+
hooks: /** @type {any[]} */ ([]), hookIndex: 0,
|
|
776
|
+
effects: /** @type {Array<() => void>} */ ([]),
|
|
777
|
+
cleanups: /** @type {Array<() => void>} */ ([]),
|
|
771
778
|
mounted: false, disposed: false,
|
|
772
779
|
_parentCtx: componentStack[componentStack.length - 1] || null,
|
|
773
780
|
_suspenseBoundary: boundary,
|
|
@@ -795,11 +802,12 @@ function createSuspenseBoundary(vnode, parent) {
|
|
|
795
802
|
componentStack.push(boundaryCtx);
|
|
796
803
|
|
|
797
804
|
// Remove old content between comment boundaries
|
|
798
|
-
|
|
805
|
+
const openParent = startComment.parentNode;
|
|
806
|
+
if (openParent) {
|
|
799
807
|
while (startComment.nextSibling && startComment.nextSibling !== endComment) {
|
|
800
808
|
const old = startComment.nextSibling;
|
|
801
809
|
disposeTree(old);
|
|
802
|
-
|
|
810
|
+
openParent.removeChild(old);
|
|
803
811
|
}
|
|
804
812
|
}
|
|
805
813
|
|
|
@@ -830,7 +838,7 @@ function createSuspenseBoundary(vnode, parent) {
|
|
|
830
838
|
}
|
|
831
839
|
|
|
832
840
|
// Portal component handler
|
|
833
|
-
function createPortalDOM(vnode,
|
|
841
|
+
function createPortalDOM(vnode, _parent) {
|
|
834
842
|
const { container } = vnode.props;
|
|
835
843
|
const children = vnode.children;
|
|
836
844
|
|
|
@@ -840,7 +848,9 @@ function createPortalDOM(vnode, parent) {
|
|
|
840
848
|
}
|
|
841
849
|
|
|
842
850
|
const portalCtx = {
|
|
843
|
-
hooks: []
|
|
851
|
+
hooks: /** @type {any[]} */ ([]), hookIndex: 0,
|
|
852
|
+
effects: /** @type {Array<() => void>} */ ([]),
|
|
853
|
+
cleanups: /** @type {Array<() => void>} */ ([]),
|
|
844
854
|
mounted: false, disposed: false,
|
|
845
855
|
_parentCtx: componentStack[componentStack.length - 1] || null,
|
|
846
856
|
};
|
|
@@ -942,7 +952,7 @@ function setProp(el, key, value, isSvg) {
|
|
|
942
952
|
if (typeof value === 'function' && !_isEventProp(key) && key !== 'ref') {
|
|
943
953
|
if (!el._propEffects) el._propEffects = {};
|
|
944
954
|
if (el._propEffects[key]) {
|
|
945
|
-
try { el._propEffects[key](); } catch
|
|
955
|
+
try { el._propEffects[key](); } catch { /* already disposed */ }
|
|
946
956
|
}
|
|
947
957
|
el._propEffects[key] = effect(() => {
|
|
948
958
|
const resolved = value();
|
|
@@ -1056,7 +1066,7 @@ function setProp(el, key, value, isSvg) {
|
|
|
1056
1066
|
// reset first so removeAttribute() clears both the attribute and the property.
|
|
1057
1067
|
if (value == null) {
|
|
1058
1068
|
if (key in el) {
|
|
1059
|
-
try { el[key] = ''; } catch
|
|
1069
|
+
try { el[key] = ''; } catch { /* read-only reflected prop */ }
|
|
1060
1070
|
}
|
|
1061
1071
|
el.removeAttribute(key);
|
|
1062
1072
|
return;
|