what-core 0.5.0 → 0.5.3
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/index.d.ts +1 -0
- package/package.json +4 -4
- package/render.d.ts +1 -0
- package/src/components.js +1 -1
- package/src/dom.js +137 -24
- package/src/hooks.js +5 -0
- package/src/index.js +1 -1
- package/src/reactive.js +19 -1
- package/src/render.js +3 -3
package/index.d.ts
CHANGED
|
@@ -70,6 +70,7 @@ export function mapArray<T>(
|
|
|
70
70
|
options?: { key?: (item: T) => string | number; raw?: boolean },
|
|
71
71
|
): (parent: Node, marker?: Node | null) => Node;
|
|
72
72
|
export function spread(el: Element, props: Record<string, any>): void;
|
|
73
|
+
export function setProp(el: Element, key: string, value: any): void;
|
|
73
74
|
export function delegateEvents(eventNames: string[]): void;
|
|
74
75
|
export function on(el: Element, event: string, handler: (e: Event) => void): () => void;
|
|
75
76
|
export function classList(el: Element, classes: Record<string, boolean | (() => boolean)>): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "What Framework - The closest framework to vanilla JS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/what.js",
|
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
"license": "MIT",
|
|
49
49
|
"repository": {
|
|
50
50
|
"type": "git",
|
|
51
|
-
"url": "https://github.com/
|
|
51
|
+
"url": "https://github.com/zvndev/what-fw"
|
|
52
52
|
},
|
|
53
53
|
"bugs": {
|
|
54
|
-
"url": "https://github.com/
|
|
54
|
+
"url": "https://github.com/zvndev/what-fw/issues"
|
|
55
55
|
},
|
|
56
|
-
"homepage": "https://
|
|
56
|
+
"homepage": "https://whatframework.dev"
|
|
57
57
|
}
|
package/render.d.ts
CHANGED
package/src/components.js
CHANGED
|
@@ -50,7 +50,7 @@ export function memo(Component, areEqual) {
|
|
|
50
50
|
let _getCurrentComponent = null;
|
|
51
51
|
export function _injectGetCurrentComponent(fn) { _getCurrentComponent = fn; }
|
|
52
52
|
|
|
53
|
-
function shallowEqual(a, b) {
|
|
53
|
+
export function shallowEqual(a, b) {
|
|
54
54
|
if (a === b) return true;
|
|
55
55
|
const keysA = Object.keys(a);
|
|
56
56
|
const keysB = Object.keys(b);
|
package/src/dom.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// No virtual DOM tree kept in memory — we diff against the live DOM.
|
|
5
5
|
|
|
6
6
|
import { effect, batch, untrack, signal } from './reactive.js';
|
|
7
|
-
import { reportError, _injectGetCurrentComponent } from './components.js';
|
|
7
|
+
import { reportError, _injectGetCurrentComponent, shallowEqual } from './components.js';
|
|
8
8
|
import { _setComponentRef } from './helpers.js';
|
|
9
9
|
|
|
10
10
|
// Register <what-c> custom element to prevent flash of unstyled content
|
|
@@ -14,6 +14,36 @@ if (typeof customElements !== 'undefined' && !customElements.get('what-c')) {
|
|
|
14
14
|
connectedCallback() {
|
|
15
15
|
this.style.display = 'contents';
|
|
16
16
|
}
|
|
17
|
+
// display:contents elements don't generate a layout box — getBoundingClientRect()
|
|
18
|
+
// returns zeros, offsetWidth/Height return 0. React libraries (react-draggable,
|
|
19
|
+
// react-colorful, etc.) traverse parentNode and call getBoundingClientRect() on
|
|
20
|
+
// what they expect to be a layout container. Since <what-c> is layout-invisible,
|
|
21
|
+
// delegate to the nearest ancestor that has a real box.
|
|
22
|
+
_layoutParent() {
|
|
23
|
+
let el = this.parentElement;
|
|
24
|
+
while (el && el.tagName === 'WHAT-C') el = el.parentElement;
|
|
25
|
+
return el;
|
|
26
|
+
}
|
|
27
|
+
getBoundingClientRect() {
|
|
28
|
+
const p = this._layoutParent();
|
|
29
|
+
return p ? p.getBoundingClientRect() : super.getBoundingClientRect();
|
|
30
|
+
}
|
|
31
|
+
get offsetWidth() {
|
|
32
|
+
const p = this._layoutParent();
|
|
33
|
+
return p ? p.offsetWidth : 0;
|
|
34
|
+
}
|
|
35
|
+
get offsetHeight() {
|
|
36
|
+
const p = this._layoutParent();
|
|
37
|
+
return p ? p.offsetHeight : 0;
|
|
38
|
+
}
|
|
39
|
+
get clientWidth() {
|
|
40
|
+
const p = this._layoutParent();
|
|
41
|
+
return p ? p.clientWidth : 0;
|
|
42
|
+
}
|
|
43
|
+
get clientHeight() {
|
|
44
|
+
const p = this._layoutParent();
|
|
45
|
+
return p ? p.clientHeight : 0;
|
|
46
|
+
}
|
|
17
47
|
});
|
|
18
48
|
}
|
|
19
49
|
|
|
@@ -160,6 +190,11 @@ export function createDOM(vnode, parent, isSvg) {
|
|
|
160
190
|
return document.createTextNode(String(vnode));
|
|
161
191
|
}
|
|
162
192
|
|
|
193
|
+
// Portal (string-tagged vnodes from helpers.js Portal or react-compat createPortal)
|
|
194
|
+
if (vnode.tag === '__portal') {
|
|
195
|
+
return createPortalDOM(vnode, parent);
|
|
196
|
+
}
|
|
197
|
+
|
|
163
198
|
// Component
|
|
164
199
|
if (typeof vnode.tag === 'function') {
|
|
165
200
|
return createComponent(vnode, parent, isSvg);
|
|
@@ -208,7 +243,20 @@ export function getComponentStack() {
|
|
|
208
243
|
}
|
|
209
244
|
|
|
210
245
|
function createComponent(vnode, parent, isSvg) {
|
|
211
|
-
|
|
246
|
+
let { tag: Component, props, children } = vnode;
|
|
247
|
+
|
|
248
|
+
// Class component detection — ES6 classes can't be called without `new`.
|
|
249
|
+
// React compat layer wraps class components in createElement, but some
|
|
250
|
+
// library-internal components may bypass that path. Detect and wrap here.
|
|
251
|
+
if (typeof Component === 'function' &&
|
|
252
|
+
(Component.prototype?.isReactComponent || Component.prototype?.render)) {
|
|
253
|
+
const ClassComp = Component;
|
|
254
|
+
Component = function ClassComponentBridge(props) {
|
|
255
|
+
const instance = new ClassComp(props);
|
|
256
|
+
return instance.render();
|
|
257
|
+
};
|
|
258
|
+
Component.displayName = ClassComp.displayName || ClassComp.name || 'ClassComponent';
|
|
259
|
+
}
|
|
212
260
|
|
|
213
261
|
// Handle special boundary components
|
|
214
262
|
if (Component === '__errorBoundary' || vnode.tag === '__errorBoundary') {
|
|
@@ -217,8 +265,8 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
217
265
|
if (Component === '__suspense' || vnode.tag === '__suspense') {
|
|
218
266
|
return createSuspenseBoundary(vnode, parent);
|
|
219
267
|
}
|
|
220
|
-
if (Component === '__portal' || vnode.tag === '__portal') {
|
|
221
|
-
return
|
|
268
|
+
if (Component === '__portal' || vnode.tag === '__portal') { // Now also handled in createDOM directly
|
|
269
|
+
return createPortalDOM(vnode, parent);
|
|
222
270
|
}
|
|
223
271
|
|
|
224
272
|
// Component context for hooks
|
|
@@ -258,7 +306,9 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
258
306
|
mountedComponents.add(ctx);
|
|
259
307
|
|
|
260
308
|
// Props signal for reactive updates from parent
|
|
261
|
-
|
|
309
|
+
// Match React's children semantics: 0→undefined, 1→single child, N→array
|
|
310
|
+
const propsChildren = children.length === 0 ? undefined : children.length === 1 ? children[0] : children;
|
|
311
|
+
const propsSignal = signal({ ...props, children: propsChildren });
|
|
262
312
|
ctx._propsSignal = propsSignal;
|
|
263
313
|
|
|
264
314
|
// Reactive render: re-renders when signals used inside change
|
|
@@ -280,7 +330,9 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
280
330
|
return;
|
|
281
331
|
}
|
|
282
332
|
|
|
283
|
-
componentStack
|
|
333
|
+
// Keep ctx on componentStack while creating/reconciling children
|
|
334
|
+
// so child components' _parentCtx correctly points to this component.
|
|
335
|
+
// This is essential for context propagation (useContext walks _parentCtx).
|
|
284
336
|
|
|
285
337
|
const vnodes = Array.isArray(result) ? result : [result];
|
|
286
338
|
|
|
@@ -306,6 +358,8 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
306
358
|
// Update: reconcile children inside wrapper
|
|
307
359
|
reconcileChildren(wrapper, vnodes);
|
|
308
360
|
}
|
|
361
|
+
|
|
362
|
+
componentStack.pop();
|
|
309
363
|
});
|
|
310
364
|
|
|
311
365
|
ctx.effects.push(dispose);
|
|
@@ -343,7 +397,6 @@ function createErrorBoundary(vnode, parent) {
|
|
|
343
397
|
vnodes = children;
|
|
344
398
|
}
|
|
345
399
|
|
|
346
|
-
componentStack.pop();
|
|
347
400
|
vnodes = Array.isArray(vnodes) ? vnodes : [vnodes];
|
|
348
401
|
|
|
349
402
|
if (wrapper.childNodes.length === 0) {
|
|
@@ -354,6 +407,8 @@ function createErrorBoundary(vnode, parent) {
|
|
|
354
407
|
} else {
|
|
355
408
|
reconcileChildren(wrapper, vnodes);
|
|
356
409
|
}
|
|
410
|
+
|
|
411
|
+
componentStack.pop();
|
|
357
412
|
});
|
|
358
413
|
|
|
359
414
|
boundaryCtx.effects.push(dispose);
|
|
@@ -381,6 +436,8 @@ function createSuspenseBoundary(vnode, parent) {
|
|
|
381
436
|
const vnodes = isLoading ? [fallback] : children;
|
|
382
437
|
const normalized = Array.isArray(vnodes) ? vnodes : [vnodes];
|
|
383
438
|
|
|
439
|
+
componentStack.push(boundaryCtx);
|
|
440
|
+
|
|
384
441
|
if (wrapper.childNodes.length === 0) {
|
|
385
442
|
for (const v of normalized) {
|
|
386
443
|
const node = createDOM(v, wrapper);
|
|
@@ -389,6 +446,8 @@ function createSuspenseBoundary(vnode, parent) {
|
|
|
389
446
|
} else {
|
|
390
447
|
reconcileChildren(wrapper, normalized);
|
|
391
448
|
}
|
|
449
|
+
|
|
450
|
+
componentStack.pop();
|
|
392
451
|
});
|
|
393
452
|
|
|
394
453
|
boundaryCtx.effects.push(dispose);
|
|
@@ -396,7 +455,7 @@ function createSuspenseBoundary(vnode, parent) {
|
|
|
396
455
|
}
|
|
397
456
|
|
|
398
457
|
// Portal component handler — renders children into a different DOM container
|
|
399
|
-
function
|
|
458
|
+
function createPortalDOM(vnode, parent) {
|
|
400
459
|
const { container } = vnode.props;
|
|
401
460
|
const children = vnode.children;
|
|
402
461
|
|
|
@@ -494,9 +553,33 @@ function reconcileUnkeyed(parent, oldNodes, newVNodes, beforeMarker) {
|
|
|
494
553
|
|
|
495
554
|
// Keyed reconciliation with LIS algorithm for O(n log n) minimal moves
|
|
496
555
|
function reconcileKeyed(parent, oldNodes, newVNodes, beforeMarker) {
|
|
556
|
+
const newLen = newVNodes.length;
|
|
557
|
+
const oldLen = oldNodes.length;
|
|
558
|
+
|
|
559
|
+
// --- Fast path: same-position keys (covers "update N items in-place") ---
|
|
560
|
+
// If same length and all keys match at the same index, skip LIS entirely.
|
|
561
|
+
// Just patch each node in-place — O(n) with zero DOM moves.
|
|
562
|
+
if (newLen === oldLen && newLen > 0) {
|
|
563
|
+
let allMatch = true;
|
|
564
|
+
for (let i = 0; i < newLen; i++) {
|
|
565
|
+
const newKey = newVNodes[i]?.key;
|
|
566
|
+
const oldKey = oldNodes[i]?._vnode?.key;
|
|
567
|
+
if (newKey == null || newKey !== oldKey) {
|
|
568
|
+
allMatch = false;
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (allMatch) {
|
|
573
|
+
for (let i = 0; i < newLen; i++) {
|
|
574
|
+
patchNode(parent, oldNodes[i], newVNodes[i]);
|
|
575
|
+
}
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
497
580
|
// Build old key -> { node, index } map
|
|
498
581
|
const oldKeyMap = new Map();
|
|
499
|
-
for (let i = 0; i <
|
|
582
|
+
for (let i = 0; i < oldLen; i++) {
|
|
500
583
|
const node = oldNodes[i];
|
|
501
584
|
const key = node._vnode?.key;
|
|
502
585
|
if (key != null) {
|
|
@@ -505,7 +588,6 @@ function reconcileKeyed(parent, oldNodes, newVNodes, beforeMarker) {
|
|
|
505
588
|
}
|
|
506
589
|
|
|
507
590
|
const newNodes = [];
|
|
508
|
-
const newLen = newVNodes.length;
|
|
509
591
|
|
|
510
592
|
// First pass: match keys and find reusable nodes
|
|
511
593
|
const sources = new Array(newLen).fill(-1); // Maps new index to old index
|
|
@@ -522,7 +604,7 @@ function reconcileKeyed(parent, oldNodes, newVNodes, beforeMarker) {
|
|
|
522
604
|
}
|
|
523
605
|
|
|
524
606
|
// Remove nodes that aren't reused
|
|
525
|
-
for (let i = 0; i <
|
|
607
|
+
for (let i = 0; i < oldLen; i++) {
|
|
526
608
|
if (!reused.has(i) && oldNodes[i]?.parentNode) {
|
|
527
609
|
disposeTree(oldNodes[i]);
|
|
528
610
|
oldNodes[i].parentNode.removeChild(oldNodes[i]);
|
|
@@ -794,7 +876,14 @@ function patchNode(parent, domNode, vnode) {
|
|
|
794
876
|
if (domNode._componentCtx && !domNode._componentCtx.disposed
|
|
795
877
|
&& domNode._componentCtx.Component === vnode.tag) {
|
|
796
878
|
// Same component — update props reactively, let its effect re-render
|
|
797
|
-
|
|
879
|
+
const ch = vnode.children;
|
|
880
|
+
const patchChildren = ch.length === 0 ? undefined : ch.length === 1 ? ch[0] : ch;
|
|
881
|
+
const nextProps = { ...vnode.props, children: patchChildren };
|
|
882
|
+
// Skip signal update if props haven't changed (shallow compare)
|
|
883
|
+
const prevProps = domNode._componentCtx._propsSignal.peek();
|
|
884
|
+
if (!shallowEqual(prevProps, nextProps)) {
|
|
885
|
+
domNode._componentCtx._propsSignal.set(nextProps);
|
|
886
|
+
}
|
|
798
887
|
domNode._vnode = vnode; // Keep vnode current for keyed reconciliation
|
|
799
888
|
return domNode;
|
|
800
889
|
}
|
|
@@ -905,23 +994,40 @@ function applyProps(el, newProps, oldProps, isSvg) {
|
|
|
905
994
|
}
|
|
906
995
|
|
|
907
996
|
function setProp(el, key, value, isSvg) {
|
|
908
|
-
// Event handlers: onClick -> click
|
|
997
|
+
// Event handlers: onClick -> click, onFocusCapture -> focus (capture phase)
|
|
909
998
|
// Wrap in untrack so signal reads in handlers don't create subscriptions
|
|
910
999
|
if (key.startsWith('on') && key.length > 2) {
|
|
911
|
-
|
|
1000
|
+
let eventName = key.slice(2);
|
|
1001
|
+
// React-style capture phase: onClickCapture → click in capture phase
|
|
1002
|
+
let useCapture = false;
|
|
1003
|
+
if (eventName.endsWith('Capture')) {
|
|
1004
|
+
eventName = eventName.slice(0, -7);
|
|
1005
|
+
useCapture = true;
|
|
1006
|
+
}
|
|
1007
|
+
const event = eventName.toLowerCase();
|
|
1008
|
+
// Use a combined key for storage so capture/bubble don't conflict
|
|
1009
|
+
const storageKey = useCapture ? event + '_capture' : event;
|
|
912
1010
|
// Store handler for removal
|
|
913
|
-
const old = el._events?.[
|
|
1011
|
+
const old = el._events?.[storageKey];
|
|
914
1012
|
// Skip re-wrapping if same handler function
|
|
915
1013
|
if (old && old._original === value) return;
|
|
916
|
-
if (old) el.removeEventListener(event, old);
|
|
1014
|
+
if (old) el.removeEventListener(event, old, useCapture);
|
|
1015
|
+
// If handler is null/undefined, just remove the old one and bail
|
|
1016
|
+
if (value == null) return;
|
|
917
1017
|
if (!el._events) el._events = {};
|
|
918
|
-
// Wrap handler to untrack signal reads
|
|
919
|
-
|
|
1018
|
+
// Wrap handler to untrack signal reads.
|
|
1019
|
+
// Add nativeEvent for React compat — React synthetic events have
|
|
1020
|
+
// e.nativeEvent pointing to the actual DOM event. Libraries like
|
|
1021
|
+
// react-colorful, cmdk, and @floating-ui/react check this property.
|
|
1022
|
+
const wrappedHandler = (e) => {
|
|
1023
|
+
if (!e.nativeEvent) e.nativeEvent = e;
|
|
1024
|
+
return untrack(() => value(e));
|
|
1025
|
+
};
|
|
920
1026
|
wrappedHandler._original = value;
|
|
921
|
-
el._events[
|
|
1027
|
+
el._events[storageKey] = wrappedHandler;
|
|
922
1028
|
// Check for _eventOpts (once/capture/passive from compiler)
|
|
923
1029
|
const eventOpts = value._eventOpts;
|
|
924
|
-
el.addEventListener(event, wrappedHandler, eventOpts || undefined);
|
|
1030
|
+
el.addEventListener(event, wrappedHandler, eventOpts || useCapture || undefined);
|
|
925
1031
|
return;
|
|
926
1032
|
}
|
|
927
1033
|
|
|
@@ -1003,10 +1109,17 @@ function setProp(el, key, value, isSvg) {
|
|
|
1003
1109
|
|
|
1004
1110
|
function removeProp(el, key, oldValue) {
|
|
1005
1111
|
if (key.startsWith('on') && key.length > 2) {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1112
|
+
let eventName = key.slice(2);
|
|
1113
|
+
let useCapture = false;
|
|
1114
|
+
if (eventName.endsWith('Capture')) {
|
|
1115
|
+
eventName = eventName.slice(0, -7);
|
|
1116
|
+
useCapture = true;
|
|
1117
|
+
}
|
|
1118
|
+
const event = eventName.toLowerCase();
|
|
1119
|
+
const storageKey = useCapture ? event + '_capture' : event;
|
|
1120
|
+
if (el._events?.[storageKey]) {
|
|
1121
|
+
el.removeEventListener(event, el._events[storageKey], useCapture);
|
|
1122
|
+
delete el._events[storageKey];
|
|
1010
1123
|
}
|
|
1011
1124
|
return;
|
|
1012
1125
|
}
|
package/src/hooks.js
CHANGED
|
@@ -192,6 +192,11 @@ export function createContext(defaultValue) {
|
|
|
192
192
|
}
|
|
193
193
|
return children;
|
|
194
194
|
},
|
|
195
|
+
// React-compatible Consumer: <Context.Consumer>{value => ...}</Context.Consumer>
|
|
196
|
+
Consumer: ({ children }) => {
|
|
197
|
+
const value = useContext(context);
|
|
198
|
+
return typeof children === 'function' ? children(value) : children;
|
|
199
|
+
},
|
|
195
200
|
};
|
|
196
201
|
return context;
|
|
197
202
|
}
|
package/src/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
export { signal, computed, effect, memo as signalMemo, batch, untrack, flushSync, createRoot } from './reactive.js';
|
|
6
6
|
|
|
7
7
|
// Fine-grained rendering primitives
|
|
8
|
-
export { template, insert, mapArray, spread, delegateEvents, on, classList } from './render.js';
|
|
8
|
+
export { template, insert, mapArray, spread, setProp, delegateEvents, on, classList } from './render.js';
|
|
9
9
|
|
|
10
10
|
// Virtual DOM
|
|
11
11
|
export { h, Fragment, html } from './h.js';
|
package/src/reactive.js
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
// Dev-mode flag — build tools can dead-code-eliminate when false
|
|
5
5
|
export const __DEV__ = typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production' || true;
|
|
6
6
|
|
|
7
|
+
// DevTools hooks — set by what-devtools when installed.
|
|
8
|
+
// These are no-ops in production (dead-code eliminated with __DEV__).
|
|
9
|
+
export let __devtools = null;
|
|
10
|
+
|
|
11
|
+
/** @internal Install devtools hooks. Called by what-devtools. */
|
|
12
|
+
export function __setDevToolsHooks(hooks) {
|
|
13
|
+
if (__DEV__) __devtools = hooks;
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
let currentEffect = null;
|
|
8
17
|
let currentRoot = null;
|
|
9
18
|
let batchDepth = 0;
|
|
@@ -31,6 +40,7 @@ export function signal(initial) {
|
|
|
31
40
|
const nextVal = typeof args[0] === 'function' ? args[0](value) : args[0];
|
|
32
41
|
if (Object.is(value, nextVal)) return;
|
|
33
42
|
value = nextVal;
|
|
43
|
+
if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);
|
|
34
44
|
notify(subs);
|
|
35
45
|
}
|
|
36
46
|
|
|
@@ -38,6 +48,7 @@ export function signal(initial) {
|
|
|
38
48
|
const nextVal = typeof next === 'function' ? next(value) : next;
|
|
39
49
|
if (Object.is(value, nextVal)) return;
|
|
40
50
|
value = nextVal;
|
|
51
|
+
if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);
|
|
41
52
|
notify(subs);
|
|
42
53
|
};
|
|
43
54
|
|
|
@@ -48,6 +59,10 @@ export function signal(initial) {
|
|
|
48
59
|
};
|
|
49
60
|
|
|
50
61
|
sig._signal = true;
|
|
62
|
+
|
|
63
|
+
// Notify devtools of signal creation
|
|
64
|
+
if (__DEV__ && __devtools) __devtools.onSignalCreate(sig);
|
|
65
|
+
|
|
51
66
|
return sig;
|
|
52
67
|
}
|
|
53
68
|
|
|
@@ -130,7 +145,7 @@ export function batch(fn) {
|
|
|
130
145
|
// --- Internals ---
|
|
131
146
|
|
|
132
147
|
function _createEffect(fn, lazy) {
|
|
133
|
-
|
|
148
|
+
const e = {
|
|
134
149
|
fn,
|
|
135
150
|
deps: [], // array of subscriber sets (cheaper than Set for typical 1-3 deps)
|
|
136
151
|
lazy: lazy || false,
|
|
@@ -139,6 +154,8 @@ function _createEffect(fn, lazy) {
|
|
|
139
154
|
_pending: false,
|
|
140
155
|
_stable: false, // stable effects skip cleanup/re-subscribe on re-run
|
|
141
156
|
};
|
|
157
|
+
if (__DEV__ && __devtools) __devtools.onEffectCreate(e);
|
|
158
|
+
return e;
|
|
142
159
|
}
|
|
143
160
|
|
|
144
161
|
function _runEffect(e) {
|
|
@@ -187,6 +204,7 @@ function _runEffect(e) {
|
|
|
187
204
|
|
|
188
205
|
function _disposeEffect(e) {
|
|
189
206
|
e.disposed = true;
|
|
207
|
+
if (__DEV__ && __devtools) __devtools.onEffectDispose(e);
|
|
190
208
|
cleanup(e);
|
|
191
209
|
// Run cleanup on dispose
|
|
192
210
|
if (e._cleanup) {
|
package/src/render.js
CHANGED
|
@@ -693,16 +693,16 @@ export function spread(el, props) {
|
|
|
693
693
|
}
|
|
694
694
|
});
|
|
695
695
|
} else {
|
|
696
|
-
effect(() => {
|
|
696
|
+
effect(() => { setProp(el, key, value()); });
|
|
697
697
|
}
|
|
698
698
|
} else {
|
|
699
699
|
// Static prop
|
|
700
|
-
|
|
700
|
+
setProp(el, key, value);
|
|
701
701
|
}
|
|
702
702
|
}
|
|
703
703
|
}
|
|
704
704
|
|
|
705
|
-
function
|
|
705
|
+
export function setProp(el, key, value) {
|
|
706
706
|
if (key === 'class' || key === 'className') {
|
|
707
707
|
el.className = value || '';
|
|
708
708
|
} else if (key === 'dangerouslySetInnerHTML') {
|