@simpreact/simpreact 0.0.2 → 0.0.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/compat/core.d.ts +46 -0
- package/compat/core.js +93 -0
- package/compat/dom.d.ts +11 -0
- package/compat/dom.js +10 -0
- package/compat/hooks.d.ts +26 -0
- package/compat/hooks.js +77 -0
- package/compat/index.d.ts +42 -0
- package/compat/index.js +14 -0
- package/compat/jsx-runtime.d.ts +10 -0
- package/compat/jsx-runtime.js +9 -0
- package/compat/utils.d.ts +1 -0
- package/compat/utils.js +3 -0
- package/core/createElement.d.ts +1 -1
- package/core/createElement.js +3 -1
- package/core/index.d.ts +2 -2
- package/core/lifecycleEventBus.d.ts +4 -0
- package/core/mounting.js +30 -3
- package/core/patching.js +51 -11
- package/core/ref.d.ts +4 -4
- package/core/rerender.d.ts +4 -4
- package/core/rerender.js +31 -24
- package/core/unmounting.js +4 -0
- package/dom/attach-element-to-dom.js +1 -1
- package/dom/events.d.ts +3 -0
- package/dom/events.js +9 -3
- package/dom/props/controlled/input.js +5 -5
- package/dom/props/controlled/select.js +3 -3
- package/dom/props/controlled/textarea.js +5 -5
- package/dom/props/style.js +6 -3
- package/dom/render.js +3 -3
- package/hooks/index.d.ts +6 -0
- package/hooks/index.js +42 -9
- package/package.json +7 -1
- package/shared/utils.d.ts +1 -0
- package/shared/utils.js +16 -0
package/compat/core.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as SimpReactInternal from '../core/internal.js';
|
|
2
|
+
import { identity } from './utils.js';
|
|
3
|
+
export declare const Children: {
|
|
4
|
+
map(children: SimpReactInternal.SimpNode, fn: (child: SimpReactInternal.SimpNode, index: number) => SimpReactInternal.SimpNode): SimpReactInternal.SimpNode[];
|
|
5
|
+
forEach(children: SimpReactInternal.SimpNode, fn: (child: SimpReactInternal.SimpNode, index: number) => void): void;
|
|
6
|
+
count(children: SimpReactInternal.SimpNode): number;
|
|
7
|
+
toArray(children: SimpReactInternal.SimpNode): SimpReactInternal.SimpNode[];
|
|
8
|
+
only(children: SimpReactInternal.SimpNode): SimpReactInternal.SimpElement;
|
|
9
|
+
};
|
|
10
|
+
export declare function cloneElement(element: SimpReactInternal.SimpElement, props?: any, ...children: SimpReactInternal.SimpNode[]): SimpReactInternal.SimpElement;
|
|
11
|
+
export declare function isValidElement(element: unknown): element is SimpReactInternal.SimpElement;
|
|
12
|
+
export declare function Suspense(props: {
|
|
13
|
+
fallback: SimpReactInternal.SimpNode;
|
|
14
|
+
children: SimpReactInternal.SimpNode;
|
|
15
|
+
}): SimpReactInternal.SimpNode;
|
|
16
|
+
export declare function forwardRef<P, T>(Component: (props: P, ref: SimpReactInternal.Ref<T>) => any): (props: P) => any;
|
|
17
|
+
export declare const Fragment: SimpReactInternal.Fragment;
|
|
18
|
+
export declare const createElement: typeof SimpReactInternal.createElement;
|
|
19
|
+
export declare const createContext: typeof SimpReactInternal.createContext;
|
|
20
|
+
export declare const createPortal: typeof SimpReactInternal.createPortal;
|
|
21
|
+
export declare const memo: typeof identity;
|
|
22
|
+
export declare const flushSync: typeof identity;
|
|
23
|
+
export declare class Component {
|
|
24
|
+
constructor();
|
|
25
|
+
}
|
|
26
|
+
declare const _default: {
|
|
27
|
+
Children: {
|
|
28
|
+
map(children: SimpReactInternal.SimpNode, fn: (child: SimpReactInternal.SimpNode, index: number) => SimpReactInternal.SimpNode): SimpReactInternal.SimpNode[];
|
|
29
|
+
forEach(children: SimpReactInternal.SimpNode, fn: (child: SimpReactInternal.SimpNode, index: number) => void): void;
|
|
30
|
+
count(children: SimpReactInternal.SimpNode): number;
|
|
31
|
+
toArray(children: SimpReactInternal.SimpNode): SimpReactInternal.SimpNode[];
|
|
32
|
+
only(children: SimpReactInternal.SimpNode): SimpReactInternal.SimpElement;
|
|
33
|
+
};
|
|
34
|
+
cloneElement: typeof cloneElement;
|
|
35
|
+
isValidElement: typeof isValidElement;
|
|
36
|
+
Suspense: typeof Suspense;
|
|
37
|
+
forwardRef: typeof forwardRef;
|
|
38
|
+
Fragment: SimpReactInternal.Fragment;
|
|
39
|
+
createElement: typeof SimpReactInternal.createElement;
|
|
40
|
+
createContext: typeof SimpReactInternal.createContext;
|
|
41
|
+
createPortal: typeof SimpReactInternal.createPortal;
|
|
42
|
+
memo: typeof identity;
|
|
43
|
+
flushSync: typeof identity;
|
|
44
|
+
Component: typeof Component;
|
|
45
|
+
};
|
|
46
|
+
export default _default;
|
package/compat/core.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as SimpReactInternal from '../core/internal.js';
|
|
2
|
+
import * as SimpReactHooks from '../hooks/index.js';
|
|
3
|
+
import { identity } from './utils.js';
|
|
4
|
+
export const Children = {
|
|
5
|
+
map(children, fn) {
|
|
6
|
+
return Children.toArray(children).map(fn);
|
|
7
|
+
},
|
|
8
|
+
forEach(children, fn) {
|
|
9
|
+
Children.toArray(children).forEach(fn);
|
|
10
|
+
},
|
|
11
|
+
count(children) {
|
|
12
|
+
return Children.toArray(children).length;
|
|
13
|
+
},
|
|
14
|
+
toArray(children) {
|
|
15
|
+
const result = [];
|
|
16
|
+
function traverse(node) {
|
|
17
|
+
if (node == null || typeof node === 'boolean') {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(node)) {
|
|
21
|
+
for (const child of node) {
|
|
22
|
+
traverse(child);
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
result.push(node);
|
|
27
|
+
}
|
|
28
|
+
traverse(children);
|
|
29
|
+
return result;
|
|
30
|
+
},
|
|
31
|
+
only(children) {
|
|
32
|
+
const array = Children.toArray(children);
|
|
33
|
+
if (array.length !== 1 || !isValidElement(array[0])) {
|
|
34
|
+
throw new Error('Children.only expected a single SimpElement child.');
|
|
35
|
+
}
|
|
36
|
+
return array[0];
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export function cloneElement(element, props, ...children) {
|
|
40
|
+
if (!isValidElement(element)) {
|
|
41
|
+
throw new Error(`cloneElement: expected a SimpElement, got ${element}`);
|
|
42
|
+
}
|
|
43
|
+
if (element.flag === 'PORTAL') {
|
|
44
|
+
throw new Error('cloneElement: the argument must be a SimpElement, but you passed a portal instead.');
|
|
45
|
+
}
|
|
46
|
+
return SimpReactInternal.createElement(element.flag === 'FRAGMENT' ? SimpReactInternal.Fragment : element.type, Object.assign({}, element.props, props), arguments.length > 2 ? children : props.children || element.children);
|
|
47
|
+
}
|
|
48
|
+
export function isValidElement(element) {
|
|
49
|
+
return typeof element === 'object' && element !== null && 'flag' in element;
|
|
50
|
+
}
|
|
51
|
+
export function Suspense(props) {
|
|
52
|
+
const [isSuspended, setIsSuspended] = SimpReactHooks.useState(false);
|
|
53
|
+
SimpReactHooks.useCatch(error => {
|
|
54
|
+
if (isSuspended) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (error instanceof Promise) {
|
|
58
|
+
setIsSuspended(true);
|
|
59
|
+
error.then(() => setIsSuspended(false));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return isSuspended ? props.fallback : props.children;
|
|
63
|
+
}
|
|
64
|
+
export function forwardRef(Component) {
|
|
65
|
+
return function Forwarded(props) {
|
|
66
|
+
return Component(props, props?.ref || null);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export const Fragment = SimpReactInternal.Fragment;
|
|
70
|
+
export const createElement = SimpReactInternal.createElement;
|
|
71
|
+
export const createContext = SimpReactInternal.createContext;
|
|
72
|
+
export const createPortal = SimpReactInternal.createPortal;
|
|
73
|
+
export const memo = identity;
|
|
74
|
+
export const flushSync = identity;
|
|
75
|
+
export class Component {
|
|
76
|
+
constructor() {
|
|
77
|
+
throw new Error('Not implemented.');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export default {
|
|
81
|
+
Children,
|
|
82
|
+
cloneElement,
|
|
83
|
+
isValidElement,
|
|
84
|
+
Suspense,
|
|
85
|
+
forwardRef,
|
|
86
|
+
Fragment,
|
|
87
|
+
createElement,
|
|
88
|
+
createContext,
|
|
89
|
+
createPortal,
|
|
90
|
+
memo,
|
|
91
|
+
flushSync,
|
|
92
|
+
Component,
|
|
93
|
+
};
|
package/compat/dom.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as SimpReactDOM from '../dom/index.js';
|
|
2
|
+
import * as SimpReactShared from '../shared/index.js';
|
|
3
|
+
export declare const hydrate: typeof SimpReactShared.noop;
|
|
4
|
+
export declare const render: typeof SimpReactDOM.render;
|
|
5
|
+
export declare const createRoot: typeof SimpReactDOM.createRoot;
|
|
6
|
+
declare const _default: {
|
|
7
|
+
hydrate: typeof SimpReactShared.noop;
|
|
8
|
+
render: typeof SimpReactDOM.render;
|
|
9
|
+
createRoot: typeof SimpReactDOM.createRoot;
|
|
10
|
+
};
|
|
11
|
+
export default _default;
|
package/compat/dom.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as SimpReactDOM from '../dom/index.js';
|
|
2
|
+
import * as SimpReactShared from '../shared/index.js';
|
|
3
|
+
export const hydrate = SimpReactShared.noop;
|
|
4
|
+
export const render = SimpReactDOM.render;
|
|
5
|
+
export const createRoot = SimpReactDOM.createRoot;
|
|
6
|
+
export default {
|
|
7
|
+
hydrate,
|
|
8
|
+
render,
|
|
9
|
+
createRoot,
|
|
10
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as SimpReactHooks from '../hooks/index.js';
|
|
2
|
+
export declare function useSyncExternalStore<T>(subscribe: (callback: () => void) => () => void, getSnapshot: () => T): T;
|
|
3
|
+
export declare function useReducer<R extends (state: any, action: any) => any, I>(reducer: R, initializerArg: I, initializer?: (arg: I) => ReturnType<R>): [ReturnType<R>, (action: Parameters<R>[1]) => void];
|
|
4
|
+
export declare function useId(prefix?: string): string;
|
|
5
|
+
export declare function useMemo<T>(factory: () => T, deps: SimpReactHooks.DependencyList): T;
|
|
6
|
+
export declare function useCallback<T>(cb: T, deps: SimpReactHooks.DependencyList): T;
|
|
7
|
+
export declare const useState: typeof SimpReactHooks.useState;
|
|
8
|
+
export declare const useEffect: typeof SimpReactHooks.useEffect;
|
|
9
|
+
export declare const useLayoutEffect: typeof SimpReactHooks.useEffect;
|
|
10
|
+
export declare const useInsertionEffect: typeof SimpReactHooks.useEffect;
|
|
11
|
+
export declare const useRef: typeof SimpReactHooks.useRef;
|
|
12
|
+
export declare const useContext: typeof SimpReactHooks.useContext;
|
|
13
|
+
declare const _default: {
|
|
14
|
+
useSyncExternalStore: typeof useSyncExternalStore;
|
|
15
|
+
useReducer: typeof useReducer;
|
|
16
|
+
useId: typeof useId;
|
|
17
|
+
useMemo: typeof useMemo;
|
|
18
|
+
useCallback: typeof useCallback;
|
|
19
|
+
useState: typeof SimpReactHooks.useState;
|
|
20
|
+
useEffect: typeof SimpReactHooks.useEffect;
|
|
21
|
+
useLayoutEffect: typeof SimpReactHooks.useEffect;
|
|
22
|
+
useInsertionEffect: typeof SimpReactHooks.useEffect;
|
|
23
|
+
useRef: typeof SimpReactHooks.useRef;
|
|
24
|
+
useContext: typeof SimpReactHooks.useContext;
|
|
25
|
+
};
|
|
26
|
+
export default _default;
|
package/compat/hooks.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as SimpReactHooks from '../hooks/index.js';
|
|
2
|
+
export function useSyncExternalStore(subscribe, getSnapshot) {
|
|
3
|
+
const rerender = SimpReactHooks.useRerender();
|
|
4
|
+
const lastSnapshotRef = SimpReactHooks.useRef(getSnapshot());
|
|
5
|
+
SimpReactHooks.useEffect(() => {
|
|
6
|
+
function checkForUpdates() {
|
|
7
|
+
const nextSnapshot = getSnapshot();
|
|
8
|
+
if (!Object.is(lastSnapshotRef.current, nextSnapshot)) {
|
|
9
|
+
lastSnapshotRef.current = nextSnapshot;
|
|
10
|
+
rerender();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const unsubscribe = subscribe(checkForUpdates);
|
|
14
|
+
checkForUpdates();
|
|
15
|
+
return unsubscribe;
|
|
16
|
+
}, [subscribe, getSnapshot]);
|
|
17
|
+
return lastSnapshotRef.current;
|
|
18
|
+
}
|
|
19
|
+
export function useReducer(reducer, initializerArg, initializer) {
|
|
20
|
+
const rerender = SimpReactHooks.useRerender();
|
|
21
|
+
const reducerRef = SimpReactHooks.useRef(reducer);
|
|
22
|
+
reducerRef.current = reducer;
|
|
23
|
+
const stateRef = SimpReactHooks.useRef([
|
|
24
|
+
initializer ? initializer(initializerArg) : initializerArg,
|
|
25
|
+
function dispatch(action) {
|
|
26
|
+
const newState = reducerRef.current(stateRef.current[0], action);
|
|
27
|
+
if (Object.is(newState, stateRef.current[0])) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
stateRef.current[0] = newState;
|
|
31
|
+
rerender();
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
return stateRef.current;
|
|
35
|
+
}
|
|
36
|
+
let globalId = 0;
|
|
37
|
+
export function useId(prefix = 'id') {
|
|
38
|
+
const idRef = SimpReactHooks.useRef('');
|
|
39
|
+
if (idRef.current === '') {
|
|
40
|
+
globalId += 1;
|
|
41
|
+
idRef.current = `${prefix}-${globalId}`;
|
|
42
|
+
}
|
|
43
|
+
return idRef.current;
|
|
44
|
+
}
|
|
45
|
+
export function useMemo(factory, deps) {
|
|
46
|
+
const ref = SimpReactHooks.useRef({
|
|
47
|
+
deps: undefined,
|
|
48
|
+
value: undefined,
|
|
49
|
+
});
|
|
50
|
+
if (!SimpReactHooks.areDepsEqual(ref.current.deps, deps)) {
|
|
51
|
+
ref.current.value = factory();
|
|
52
|
+
ref.current.deps = deps;
|
|
53
|
+
}
|
|
54
|
+
return ref.current.value;
|
|
55
|
+
}
|
|
56
|
+
export function useCallback(cb, deps) {
|
|
57
|
+
return useMemo(() => cb, deps);
|
|
58
|
+
}
|
|
59
|
+
export const useState = SimpReactHooks.useState;
|
|
60
|
+
export const useEffect = SimpReactHooks.useEffect;
|
|
61
|
+
export const useLayoutEffect = SimpReactHooks.useEffect;
|
|
62
|
+
export const useInsertionEffect = SimpReactHooks.useEffect;
|
|
63
|
+
export const useRef = SimpReactHooks.useRef;
|
|
64
|
+
export const useContext = SimpReactHooks.useContext;
|
|
65
|
+
export default {
|
|
66
|
+
useSyncExternalStore,
|
|
67
|
+
useReducer,
|
|
68
|
+
useId,
|
|
69
|
+
useMemo,
|
|
70
|
+
useCallback,
|
|
71
|
+
useState,
|
|
72
|
+
useEffect,
|
|
73
|
+
useLayoutEffect,
|
|
74
|
+
useInsertionEffect,
|
|
75
|
+
useRef,
|
|
76
|
+
useContext,
|
|
77
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export * from './core.js';
|
|
2
|
+
export * from './dom.js';
|
|
3
|
+
export * from './hooks.js';
|
|
4
|
+
export * from './jsx-runtime.js';
|
|
5
|
+
declare const _default: {
|
|
6
|
+
jsx: typeof import("../jsx-runtime/index.js").jsx;
|
|
7
|
+
jsxDEV: typeof import("../jsx-runtime/index.js").jsx;
|
|
8
|
+
jsxs: typeof import("../jsx-runtime/index.js").jsx;
|
|
9
|
+
useSyncExternalStore: typeof import("./hooks.js").useSyncExternalStore;
|
|
10
|
+
useReducer: typeof import("./hooks.js").useReducer;
|
|
11
|
+
useId: typeof import("./hooks.js").useId;
|
|
12
|
+
useMemo: typeof import("./hooks.js").useMemo;
|
|
13
|
+
useCallback: typeof import("./hooks.js").useCallback;
|
|
14
|
+
useState: typeof import("../hooks/index.js").useState;
|
|
15
|
+
useEffect: typeof import("../hooks/index.js").useEffect;
|
|
16
|
+
useLayoutEffect: typeof import("../hooks/index.js").useEffect;
|
|
17
|
+
useInsertionEffect: typeof import("../hooks/index.js").useEffect;
|
|
18
|
+
useRef: typeof import("../hooks/index.js").useRef;
|
|
19
|
+
useContext: typeof import("../hooks/index.js").useContext;
|
|
20
|
+
hydrate: typeof import("../shared/utils.js").noop;
|
|
21
|
+
render: typeof import("../dom/render.js").render;
|
|
22
|
+
createRoot: typeof import("../dom/render.js").createRoot;
|
|
23
|
+
Children: {
|
|
24
|
+
map(children: import("../core/createElement.js").SimpNode, fn: (child: import("../core/createElement.js").SimpNode, index: number) => import("../core/createElement.js").SimpNode): import("../core/createElement.js").SimpNode[];
|
|
25
|
+
forEach(children: import("../core/createElement.js").SimpNode, fn: (child: import("../core/createElement.js").SimpNode, index: number) => void): void;
|
|
26
|
+
count(children: import("../core/createElement.js").SimpNode): number;
|
|
27
|
+
toArray(children: import("../core/createElement.js").SimpNode): import("../core/createElement.js").SimpNode[];
|
|
28
|
+
only(children: import("../core/createElement.js").SimpNode): import("../core/createElement.js").SimpElement;
|
|
29
|
+
};
|
|
30
|
+
cloneElement: typeof import("./core.js").cloneElement;
|
|
31
|
+
isValidElement: typeof import("./core.js").isValidElement;
|
|
32
|
+
Suspense: typeof import("./core.js").Suspense;
|
|
33
|
+
forwardRef: typeof import("./core.js").forwardRef;
|
|
34
|
+
Fragment: import("../core/fragment.js").Fragment;
|
|
35
|
+
createElement: typeof import("../core/createElement.js").createElement;
|
|
36
|
+
createContext: typeof import("../core/context.js").createContext;
|
|
37
|
+
createPortal: typeof import("../core/portal.js").createPortal;
|
|
38
|
+
memo: typeof import("./utils.js").identity;
|
|
39
|
+
flushSync: typeof import("./utils.js").identity;
|
|
40
|
+
Component: typeof import("./core.js").Component;
|
|
41
|
+
};
|
|
42
|
+
export default _default;
|
package/compat/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import coreDefault from './core.js';
|
|
2
|
+
import domDefault from './dom.js';
|
|
3
|
+
import hooksDefault from './hooks.js';
|
|
4
|
+
import jsxRuntimeDefault from './jsx-runtime.js';
|
|
5
|
+
export * from './core.js';
|
|
6
|
+
export * from './dom.js';
|
|
7
|
+
export * from './hooks.js';
|
|
8
|
+
export * from './jsx-runtime.js';
|
|
9
|
+
export default {
|
|
10
|
+
...coreDefault,
|
|
11
|
+
...domDefault,
|
|
12
|
+
...hooksDefault,
|
|
13
|
+
...jsxRuntimeDefault,
|
|
14
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as SimpReactJSXRuntime from '../jsx-runtime/index.js';
|
|
2
|
+
export declare const jsx: typeof SimpReactJSXRuntime.jsx;
|
|
3
|
+
export declare const jsxDEV: typeof SimpReactJSXRuntime.jsx;
|
|
4
|
+
export declare const jsxs: typeof SimpReactJSXRuntime.jsx;
|
|
5
|
+
declare const _default: {
|
|
6
|
+
jsx: typeof SimpReactJSXRuntime.jsx;
|
|
7
|
+
jsxDEV: typeof SimpReactJSXRuntime.jsx;
|
|
8
|
+
jsxs: typeof SimpReactJSXRuntime.jsx;
|
|
9
|
+
};
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function identity(value: any): any;
|
package/compat/utils.js
ADDED
package/core/createElement.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface SimpElement {
|
|
|
17
17
|
flag: SimpElementFlag;
|
|
18
18
|
parent: Nullable<SimpElement>;
|
|
19
19
|
key?: Maybe<Key>;
|
|
20
|
-
type?:
|
|
20
|
+
type?: string | FunctionComponent;
|
|
21
21
|
props?: any;
|
|
22
22
|
children?: Maybe<SimpNode>;
|
|
23
23
|
className?: Maybe<string>;
|
package/core/createElement.js
CHANGED
|
@@ -31,7 +31,9 @@ export function createElement(type, props, ...children) {
|
|
|
31
31
|
definedChildren = props[propName];
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
else if (propName === 'ref'
|
|
34
|
+
else if (propName === 'ref' &&
|
|
35
|
+
// Handle only a callback ref or an object ref with `current`.
|
|
36
|
+
props[propName]) {
|
|
35
37
|
ref = {
|
|
36
38
|
value: props[propName],
|
|
37
39
|
};
|
package/core/index.d.ts
CHANGED
|
@@ -4,9 +4,9 @@ export type ComponentType<P = {}> = FunctionComponent<P>;
|
|
|
4
4
|
|
|
5
5
|
export type RefObject<T> = { current: T };
|
|
6
6
|
export type RefCallback<T> = {
|
|
7
|
-
bivarianceHack(instance: T
|
|
7
|
+
bivarianceHack(instance: T): (() => void | undefined) | void;
|
|
8
8
|
}['bivarianceHack'];
|
|
9
|
-
export type Ref<T> = RefCallback<T> | RefObject<T> | null;
|
|
9
|
+
export type Ref<T> = RefCallback<T> | RefObject<T | null> | null;
|
|
10
10
|
|
|
11
11
|
export type Key = string | number | bigint;
|
|
12
12
|
|
|
@@ -6,7 +6,11 @@ export type LifecycleEvent = {
|
|
|
6
6
|
phase: 'mounting' | 'updating';
|
|
7
7
|
} | {
|
|
8
8
|
type: 'afterRender';
|
|
9
|
+
element: SimpElement;
|
|
9
10
|
phase: 'mounting' | 'updating';
|
|
11
|
+
} | {
|
|
12
|
+
type: 'triedToRerender';
|
|
13
|
+
element: SimpElement;
|
|
10
14
|
} | {
|
|
11
15
|
type: 'mounted';
|
|
12
16
|
element: SimpElement;
|
package/core/mounting.js
CHANGED
|
@@ -3,6 +3,7 @@ import { hostAdapter } from './hostAdapter.js';
|
|
|
3
3
|
import { createTextElement, normalizeRoot } from './createElement.js';
|
|
4
4
|
import { applyRef } from './ref.js';
|
|
5
5
|
import { lifecycleEventBus } from './lifecycleEventBus.js';
|
|
6
|
+
import { batchingRerenderLocker } from './rerender.js';
|
|
6
7
|
export function mount(element, parentReference, nextReference, contextMap, hostNamespace) {
|
|
7
8
|
if (element.flag === 'TEXT') {
|
|
8
9
|
mountTextElement(element, parentReference, nextReference);
|
|
@@ -74,15 +75,41 @@ export function mountFunctionalElement(element, parentReference, nextReference,
|
|
|
74
75
|
}
|
|
75
76
|
// FC element always has Maybe<SimpElement> children due to normalization process.
|
|
76
77
|
let children;
|
|
78
|
+
let triedToRerenderUnsubscribe;
|
|
77
79
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
lifecycleEventBus.
|
|
80
|
+
let triedToRerender = false;
|
|
81
|
+
let rerenderCounter = 0;
|
|
82
|
+
triedToRerenderUnsubscribe = lifecycleEventBus.subscribe(event => {
|
|
83
|
+
if (event.type === 'triedToRerender' && event.element === element) {
|
|
84
|
+
triedToRerender = true;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
do {
|
|
88
|
+
triedToRerender = false;
|
|
89
|
+
if (++rerenderCounter >= 25) {
|
|
90
|
+
lifecycleEventBus.publish({
|
|
91
|
+
type: 'errored',
|
|
92
|
+
element,
|
|
93
|
+
error: new Error('Too many re-renders. SimpReact limits the number of renders to prevent an infinite loop.'),
|
|
94
|
+
phase: 'mounting',
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
lifecycleEventBus.publish({ type: 'beforeRender', element, phase: 'mounting' });
|
|
99
|
+
batchingRerenderLocker.lock();
|
|
100
|
+
children = element.type(element.props || emptyObject);
|
|
101
|
+
batchingRerenderLocker.flush();
|
|
102
|
+
lifecycleEventBus.publish({ type: 'afterRender', element, phase: 'mounting' });
|
|
103
|
+
} while (triedToRerender);
|
|
104
|
+
children = normalizeRoot(children, false);
|
|
81
105
|
}
|
|
82
106
|
catch (error) {
|
|
83
107
|
lifecycleEventBus.publish({ type: 'errored', element, error, phase: 'mounting' });
|
|
84
108
|
return;
|
|
85
109
|
}
|
|
110
|
+
finally {
|
|
111
|
+
triedToRerenderUnsubscribe();
|
|
112
|
+
}
|
|
86
113
|
if (children) {
|
|
87
114
|
children.parent = element;
|
|
88
115
|
mount((element.children = children), parentReference, nextReference, contextMap, hostNamespace);
|
package/core/patching.js
CHANGED
|
@@ -5,6 +5,7 @@ import { clearElementHostReference, remove, unmount } from './unmounting.js';
|
|
|
5
5
|
import { mount, mountArrayChildren } from './mounting.js';
|
|
6
6
|
import { applyRef } from './ref.js';
|
|
7
7
|
import { lifecycleEventBus } from './lifecycleEventBus.js';
|
|
8
|
+
import { batchingRerenderLocker } from './rerender.js';
|
|
8
9
|
export function patch(prevElement, nextElement, parentReference, nextReference, contextMap, hostNamespace) {
|
|
9
10
|
if (prevElement.type !== nextElement.type || prevElement.key !== nextElement.key) {
|
|
10
11
|
replaceWithNewElement(prevElement, nextElement, parentReference, contextMap, hostNamespace);
|
|
@@ -59,29 +60,69 @@ function patchHostElement(prevElement, nextElement, contextMap, hostNamespace) {
|
|
|
59
60
|
applyRef(nextElement);
|
|
60
61
|
}
|
|
61
62
|
function patchFunctionalComponent(prevElement, nextElement, parentReference, nextReference, contextMap, hostNamespace) {
|
|
62
|
-
|
|
63
|
+
const prevStore = !prevElement.store || prevElement.unmounted
|
|
64
|
+
? { hostNamespace: prevElement.store?.hostNamespace }
|
|
65
|
+
: prevElement.store;
|
|
66
|
+
prevStore.latestElement = nextElement;
|
|
67
|
+
nextElement.store = prevStore;
|
|
63
68
|
if (hostNamespace) {
|
|
64
69
|
nextElement.store.hostNamespace = hostNamespace;
|
|
65
70
|
}
|
|
66
71
|
if (contextMap) {
|
|
67
72
|
nextElement.contextMap = contextMap;
|
|
68
73
|
}
|
|
74
|
+
// FC element always has Maybe<SimpElement> children due to normalization process.
|
|
69
75
|
let nextChildren;
|
|
76
|
+
let triedToRerenderUnsubscribe;
|
|
70
77
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
lifecycleEventBus.
|
|
78
|
+
let triedToRerender = false;
|
|
79
|
+
let rerenderCounter = 0;
|
|
80
|
+
triedToRerenderUnsubscribe = lifecycleEventBus.subscribe(event => {
|
|
81
|
+
if (event.type === 'triedToRerender' && event.element === nextElement) {
|
|
82
|
+
triedToRerender = true;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
do {
|
|
86
|
+
triedToRerender = false;
|
|
87
|
+
if (++rerenderCounter >= 25) {
|
|
88
|
+
lifecycleEventBus.publish({
|
|
89
|
+
type: 'errored',
|
|
90
|
+
element: nextElement,
|
|
91
|
+
error: new Error('Too many re-renders. SimpReact limits the number of renders to prevent an infinite loop.'),
|
|
92
|
+
phase: 'updating',
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
lifecycleEventBus.publish({ type: 'beforeRender', element: nextElement, phase: 'updating' });
|
|
97
|
+
batchingRerenderLocker.lock();
|
|
98
|
+
nextChildren = nextElement.type(nextElement.props || emptyObject);
|
|
99
|
+
batchingRerenderLocker.flush();
|
|
100
|
+
lifecycleEventBus.publish({ type: 'afterRender', element: nextElement, phase: 'updating' });
|
|
101
|
+
} while (triedToRerender);
|
|
102
|
+
nextChildren = normalizeRoot(nextChildren, false);
|
|
74
103
|
}
|
|
75
104
|
catch (error) {
|
|
76
105
|
lifecycleEventBus.publish({ type: 'errored', element: nextElement, error, phase: 'updating' });
|
|
77
106
|
return;
|
|
78
107
|
}
|
|
108
|
+
finally {
|
|
109
|
+
triedToRerenderUnsubscribe();
|
|
110
|
+
}
|
|
79
111
|
const prevChildren = prevElement.children;
|
|
80
112
|
if (nextChildren) {
|
|
81
113
|
nextElement.children = nextChildren;
|
|
82
114
|
}
|
|
83
|
-
|
|
84
|
-
|
|
115
|
+
if (!prevElement.unmounted) {
|
|
116
|
+
patchChildren(prevChildren, nextChildren, parentReference, nextReference, nextElement, contextMap, hostNamespace);
|
|
117
|
+
lifecycleEventBus.publish({ type: 'updated', element: nextElement });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
prevElement.unmounted = false;
|
|
121
|
+
if (nextChildren) {
|
|
122
|
+
nextChildren.parent = nextElement;
|
|
123
|
+
mount(nextChildren, parentReference, nextReference, contextMap, hostNamespace);
|
|
124
|
+
}
|
|
125
|
+
lifecycleEventBus.publish({ type: 'mounted', element: nextElement });
|
|
85
126
|
}
|
|
86
127
|
function patchTextElement(prevElement, nextElement) {
|
|
87
128
|
nextElement.reference = prevElement.reference;
|
|
@@ -178,8 +219,7 @@ function patchChildren(prevChildren, nextChildren, parentReference, nextReferenc
|
|
|
178
219
|
patch(prevChildren, nextChildren, parentReference, nextReference, contextMap, hostNamespace);
|
|
179
220
|
}
|
|
180
221
|
else {
|
|
181
|
-
|
|
182
|
-
hostAdapter.clearNode(parentReference);
|
|
222
|
+
remove(prevChildren, parentReference);
|
|
183
223
|
}
|
|
184
224
|
}
|
|
185
225
|
else {
|
|
@@ -216,7 +256,7 @@ export function patchKeyedChildren(prevChildren, nextChildren, parentReference,
|
|
|
216
256
|
}
|
|
217
257
|
// Step 3: Mount new nodes if prev list is exhausted
|
|
218
258
|
if (prevStart > prevEnd) {
|
|
219
|
-
const before = nextChildren[nextEnd + 1]
|
|
259
|
+
const before = findHostReferenceFromElement(nextChildren[nextEnd + 1]) || nextReference;
|
|
220
260
|
for (let i = nextStart; i <= nextEnd; i++) {
|
|
221
261
|
mount(nextChildren[i], parentReference, before, contextMap, hostNamespace);
|
|
222
262
|
}
|
|
@@ -251,7 +291,7 @@ export function patchKeyedChildren(prevChildren, nextChildren, parentReference,
|
|
|
251
291
|
usedIndices.add(prevIndex);
|
|
252
292
|
}
|
|
253
293
|
else {
|
|
254
|
-
mount(nextChild, parentReference, nextChildren[i + 1]
|
|
294
|
+
mount(nextChild, parentReference, findHostReferenceFromElement(nextChildren[i + 1]) || nextReference, contextMap, hostNamespace);
|
|
255
295
|
toMove[i - nextStart] = -1;
|
|
256
296
|
}
|
|
257
297
|
}
|
|
@@ -264,7 +304,7 @@ export function patchKeyedChildren(prevChildren, nextChildren, parentReference,
|
|
|
264
304
|
// Insert in correct order
|
|
265
305
|
for (let i = nextEnd; i >= nextStart; i--) {
|
|
266
306
|
const currentChild = nextChildren[i];
|
|
267
|
-
const reference = nextChildren[i + 1]
|
|
307
|
+
const reference = findHostReferenceFromElement(nextChildren[i + 1]) || nextReference;
|
|
268
308
|
if (toMove[i - nextStart] !== -1) {
|
|
269
309
|
hostAdapter.insertBefore(parentReference, currentChild.reference, reference);
|
|
270
310
|
}
|
package/core/ref.d.ts
CHANGED
|
@@ -5,13 +5,13 @@ interface RefSimpElement extends SimpElement {
|
|
|
5
5
|
cleanup?: () => void;
|
|
6
6
|
};
|
|
7
7
|
}
|
|
8
|
-
export
|
|
8
|
+
export type RefObject<T> = {
|
|
9
9
|
current: T;
|
|
10
|
-
}
|
|
10
|
+
};
|
|
11
11
|
export type RefCallback<T> = {
|
|
12
|
-
bivarianceHack(instance: T
|
|
12
|
+
bivarianceHack(instance: T): (() => void | undefined) | void;
|
|
13
13
|
}['bivarianceHack'];
|
|
14
|
-
export type Ref<T> = RefCallback<T> | RefObject<T> | null;
|
|
14
|
+
export type Ref<T> = RefCallback<T> | RefObject<T | null> | null;
|
|
15
15
|
export declare function unmountRef(element: RefSimpElement): void;
|
|
16
16
|
export declare function applyRef(element: RefSimpElement): void;
|
|
17
17
|
export {};
|
package/core/rerender.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ export declare function rerender(element: SimpElement): void;
|
|
|
3
3
|
interface IRendererLocker {
|
|
4
4
|
_isLocked: boolean;
|
|
5
5
|
_elements: Set<SimpElement>;
|
|
6
|
-
|
|
6
|
+
_track(element: SimpElement): void;
|
|
7
|
+
_untrack(element: SimpElement): void;
|
|
7
8
|
lock(): void;
|
|
8
|
-
track(element: SimpElement): void;
|
|
9
9
|
flush(): void;
|
|
10
10
|
}
|
|
11
|
-
export declare const
|
|
12
|
-
export declare const
|
|
11
|
+
export declare const batchingRerenderLocker: IRendererLocker;
|
|
12
|
+
export declare const renderingRerenderLocker: IRendererLocker;
|
|
13
13
|
export {};
|
package/core/rerender.js
CHANGED
|
@@ -1,59 +1,66 @@
|
|
|
1
1
|
import { findParentReferenceFromElement, updateFunctionalComponent } from './patching.js';
|
|
2
|
+
import { lifecycleEventBus } from './lifecycleEventBus.js';
|
|
3
|
+
lifecycleEventBus.subscribe(event => {
|
|
4
|
+
if (event.type === 'afterRender') {
|
|
5
|
+
batchingRerenderLocker._untrack(event.element);
|
|
6
|
+
renderingRerenderLocker._untrack(event.element);
|
|
7
|
+
}
|
|
8
|
+
});
|
|
2
9
|
export function rerender(element) {
|
|
3
10
|
if (element.flag !== 'FC') {
|
|
4
11
|
throw new TypeError('Re-rendering is only supported for FC elements.');
|
|
5
12
|
}
|
|
6
13
|
if (element.unmounted) {
|
|
7
|
-
console.warn('The component unmounted.');
|
|
14
|
+
console.warn('The component is unmounted.');
|
|
8
15
|
}
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
lifecycleEventBus.publish({ type: 'triedToRerender', element });
|
|
17
|
+
if (batchingRerenderLocker._isLocked) {
|
|
18
|
+
batchingRerenderLocker._track(element);
|
|
11
19
|
return;
|
|
12
20
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
else {
|
|
17
|
-
asyncRerenderLocker.lock();
|
|
18
|
-
updateFunctionalComponent(element, findParentReferenceFromElement(element), null, element.contextMap || null, element.store.hostNamespace);
|
|
19
|
-
asyncRerenderLocker.flush();
|
|
21
|
+
if (renderingRerenderLocker._isLocked) {
|
|
22
|
+
renderingRerenderLocker._track(element);
|
|
23
|
+
return;
|
|
20
24
|
}
|
|
25
|
+
renderingRerenderLocker.lock();
|
|
26
|
+
updateFunctionalComponent(element, findParentReferenceFromElement(element), null, element.contextMap || null, element.store.hostNamespace);
|
|
27
|
+
renderingRerenderLocker.flush();
|
|
21
28
|
}
|
|
22
|
-
export const
|
|
29
|
+
export const batchingRerenderLocker = {
|
|
23
30
|
_isLocked: false,
|
|
24
31
|
_elements: new Set(),
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
_track(element) {
|
|
33
|
+
this._elements.add(element);
|
|
34
|
+
},
|
|
35
|
+
_untrack(element) {
|
|
36
|
+
this._elements.delete(element);
|
|
27
37
|
},
|
|
28
38
|
lock() {
|
|
29
39
|
this._isLocked = true;
|
|
30
40
|
},
|
|
31
|
-
track(element) {
|
|
32
|
-
this._elements.add(element);
|
|
33
|
-
},
|
|
34
41
|
flush() {
|
|
35
42
|
this._isLocked = false;
|
|
36
43
|
if (this._elements.size === 0) {
|
|
37
44
|
return;
|
|
38
45
|
}
|
|
39
46
|
for (const element of this._elements) {
|
|
40
|
-
this.
|
|
47
|
+
this._untrack(element);
|
|
41
48
|
rerender(element.store.latestElement);
|
|
42
49
|
}
|
|
43
50
|
},
|
|
44
51
|
};
|
|
45
|
-
export const
|
|
52
|
+
export const renderingRerenderLocker = {
|
|
46
53
|
_isLocked: false,
|
|
47
54
|
_elements: new Set(),
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
_track(element) {
|
|
56
|
+
this._elements.add(element);
|
|
57
|
+
},
|
|
58
|
+
_untrack(element) {
|
|
59
|
+
this._elements.delete(element);
|
|
50
60
|
},
|
|
51
61
|
lock() {
|
|
52
62
|
this._isLocked = true;
|
|
53
63
|
},
|
|
54
|
-
track(element) {
|
|
55
|
-
this._elements.add(element);
|
|
56
|
-
},
|
|
57
64
|
flush() {
|
|
58
65
|
this._isLocked = false;
|
|
59
66
|
if (this._elements.size === 0) {
|
|
@@ -61,7 +68,7 @@ export const asyncRerenderLocker = {
|
|
|
61
68
|
}
|
|
62
69
|
queueMicrotask(() => {
|
|
63
70
|
for (const element of this._elements) {
|
|
64
|
-
this.
|
|
71
|
+
this._untrack(element);
|
|
65
72
|
rerender(element.store.latestElement);
|
|
66
73
|
}
|
|
67
74
|
});
|
package/core/unmounting.js
CHANGED
|
@@ -9,6 +9,10 @@ export function unmount(element) {
|
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
if (element.flag === 'FC') {
|
|
12
|
+
// Skip — element is already unmounted.
|
|
13
|
+
if (element.unmounted) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
12
16
|
// FC element always has Maybe<SimpElement> due to normalization.
|
|
13
17
|
if (element.children) {
|
|
14
18
|
unmount(element.children);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const elementPropertyName = '__SIMP_ELEMENT__';
|
|
2
2
|
export function attachElementToDom(element, dom) {
|
|
3
|
-
if (
|
|
3
|
+
if (element.flag !== 'TEXT') {
|
|
4
4
|
Object.defineProperty(dom, elementPropertyName, { value: element, writable: true });
|
|
5
5
|
}
|
|
6
6
|
}
|
package/dom/events.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ export declare class SyntheticEvent {
|
|
|
5
5
|
currentTarget: Nullable<EventTarget>;
|
|
6
6
|
isPropagationStopped: boolean;
|
|
7
7
|
isDefaultPrevented: boolean;
|
|
8
|
+
button?: number;
|
|
9
|
+
buttons?: number;
|
|
10
|
+
pointerId?: number;
|
|
8
11
|
constructor(event: Event);
|
|
9
12
|
get target(): EventTarget | null;
|
|
10
13
|
get type(): string;
|
package/dom/events.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { batchingRerenderLocker } from '../core/internal.js';
|
|
2
2
|
import { getElementFromDom } from './attach-element-to-dom.js';
|
|
3
3
|
const eventNameByTypes = {
|
|
4
4
|
click: 'onClick',
|
|
@@ -40,8 +40,14 @@ export class SyntheticEvent {
|
|
|
40
40
|
currentTarget = null;
|
|
41
41
|
isPropagationStopped = false;
|
|
42
42
|
isDefaultPrevented = false;
|
|
43
|
+
button;
|
|
44
|
+
buttons;
|
|
45
|
+
pointerId;
|
|
43
46
|
constructor(event) {
|
|
44
47
|
this.nativeEvent = event;
|
|
48
|
+
this.button = event.button;
|
|
49
|
+
this.buttons = event.buttons;
|
|
50
|
+
this.pointerId = event.pointerId;
|
|
45
51
|
}
|
|
46
52
|
get target() {
|
|
47
53
|
return this.nativeEvent.target;
|
|
@@ -59,7 +65,7 @@ export class SyntheticEvent {
|
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
export function dispatchDelegatedEvent(event) {
|
|
62
|
-
|
|
68
|
+
batchingRerenderLocker.lock();
|
|
63
69
|
const syntheticEvent = new SyntheticEvent(event);
|
|
64
70
|
const captureHandlers = [];
|
|
65
71
|
const bubbleHandlers = [];
|
|
@@ -91,7 +97,7 @@ export function dispatchDelegatedEvent(event) {
|
|
|
91
97
|
return;
|
|
92
98
|
}
|
|
93
99
|
}
|
|
94
|
-
|
|
100
|
+
batchingRerenderLocker.flush();
|
|
95
101
|
}
|
|
96
102
|
const captureRegex = /Capture/;
|
|
97
103
|
export function patchEvent(name, prevValue, nextValue, dom) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { batchingRerenderLocker } from '../../../core/internal.js';
|
|
2
2
|
import { getElementFromDom } from '../../attach-element-to-dom.js';
|
|
3
3
|
export function isCheckedType(type) {
|
|
4
4
|
return type === 'checkbox' || type === 'radio';
|
|
@@ -12,9 +12,9 @@ function onControlledInputInput(event) {
|
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
if (element.props['onInput']) {
|
|
15
|
-
|
|
15
|
+
batchingRerenderLocker.lock();
|
|
16
16
|
element.props['onInput'](event);
|
|
17
|
-
|
|
17
|
+
batchingRerenderLocker.flush();
|
|
18
18
|
element = getElementFromDom(event.target);
|
|
19
19
|
}
|
|
20
20
|
if (element) {
|
|
@@ -27,9 +27,9 @@ function onControlledInputChange(event) {
|
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
if (element.props['onChange']) {
|
|
30
|
-
|
|
30
|
+
batchingRerenderLocker.lock();
|
|
31
31
|
element.props['onChange'](event);
|
|
32
|
-
|
|
32
|
+
batchingRerenderLocker.flush();
|
|
33
33
|
element = getElementFromDom(event.target);
|
|
34
34
|
}
|
|
35
35
|
if (element) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { batchingRerenderLocker } from '../../../core/internal.js';
|
|
2
2
|
import { emptyObject } from '../../../shared/index.js';
|
|
3
3
|
import { getElementFromDom } from '../../attach-element-to-dom.js';
|
|
4
4
|
export function isEventNameIgnored(eventName) {
|
|
@@ -10,9 +10,9 @@ function onControlledInputChange(event) {
|
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
if (element.props['onChange']) {
|
|
13
|
-
|
|
13
|
+
batchingRerenderLocker.lock();
|
|
14
14
|
element.props['onChange'](event);
|
|
15
|
-
|
|
15
|
+
batchingRerenderLocker.flush();
|
|
16
16
|
element = getElementFromDom(event.target);
|
|
17
17
|
}
|
|
18
18
|
if (element) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { batchingRerenderLocker } from '../../../core/internal.js';
|
|
2
2
|
import { getElementFromDom } from '../../attach-element-to-dom.js';
|
|
3
3
|
export function isEventNameIgnored(eventName) {
|
|
4
4
|
return eventName === 'onChange' || eventName === 'onInput';
|
|
@@ -9,9 +9,9 @@ function onControlledTextareaChange(event) {
|
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
if (element.props['onChange']) {
|
|
12
|
-
|
|
12
|
+
batchingRerenderLocker.lock();
|
|
13
13
|
element.props['onChange'](event);
|
|
14
|
-
|
|
14
|
+
batchingRerenderLocker.flush();
|
|
15
15
|
element = getElementFromDom(event.target);
|
|
16
16
|
}
|
|
17
17
|
if (element) {
|
|
@@ -24,9 +24,9 @@ function onControlledTextareaInput(event) {
|
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
if (element.props['onInput']) {
|
|
27
|
-
|
|
27
|
+
batchingRerenderLocker.lock();
|
|
28
28
|
element.props['onInput'](event);
|
|
29
|
-
|
|
29
|
+
batchingRerenderLocker.flush();
|
|
30
30
|
element = getElementFromDom(event.target);
|
|
31
31
|
}
|
|
32
32
|
if (element) {
|
package/dom/props/style.js
CHANGED
|
@@ -14,19 +14,22 @@ export function patchStyle(prevAttrValue, nextAttrValue, dom) {
|
|
|
14
14
|
for (style in nextAttrValue) {
|
|
15
15
|
value = nextAttrValue[style];
|
|
16
16
|
if (value !== prevAttrValue[style]) {
|
|
17
|
-
domStyle.setProperty(style, value);
|
|
17
|
+
domStyle.setProperty(camelToKebab(style), value);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
for (style in prevAttrValue) {
|
|
21
21
|
if (nextAttrValue[style] == null) {
|
|
22
|
-
domStyle.removeProperty(style);
|
|
22
|
+
domStyle.removeProperty(camelToKebab(style));
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
27
27
|
for (style in nextAttrValue) {
|
|
28
28
|
value = nextAttrValue[style];
|
|
29
|
-
domStyle.setProperty(style, value);
|
|
29
|
+
domStyle.setProperty(camelToKebab(style), value);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
function camelToKebab(name) {
|
|
34
|
+
return name.replace(/[A-Z]/g, match => '-' + match.toLowerCase());
|
|
35
|
+
}
|
package/dom/render.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { renderingRerenderLocker, hostAdapter, mount, patch, provideHostAdapter, remove, } from '../core/internal.js';
|
|
2
2
|
import { domAdapter } from './domAdapter.js';
|
|
3
3
|
import { attachElementToDom, getElementFromDom } from './attach-element-to-dom.js';
|
|
4
4
|
provideHostAdapter(domAdapter);
|
|
@@ -7,7 +7,7 @@ export function render(element, container) {
|
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
9
|
const currentRootElement = getElementFromDom(container);
|
|
10
|
-
|
|
10
|
+
renderingRerenderLocker.lock();
|
|
11
11
|
if (!currentRootElement) {
|
|
12
12
|
if (element) {
|
|
13
13
|
hostAdapter.clearNode(container);
|
|
@@ -27,7 +27,7 @@ export function render(element, container) {
|
|
|
27
27
|
patch(prevChildren, element, container, null, null, hostAdapter.getHostNamespaces(element, undefined)?.self);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
renderingRerenderLocker.flush();
|
|
31
31
|
}
|
|
32
32
|
export function createRoot(container) {
|
|
33
33
|
return {
|
package/hooks/index.d.ts
CHANGED
|
@@ -4,12 +4,18 @@ export type Cleanup = () => void;
|
|
|
4
4
|
export type Effect = () => void | Cleanup;
|
|
5
5
|
export type DependencyList = readonly unknown[];
|
|
6
6
|
|
|
7
|
+
export type Dispatch<A> = (value: A) => void;
|
|
8
|
+
export type SetStateAction<S> = S | ((prevState: S) => S);
|
|
9
|
+
|
|
7
10
|
declare function useRef<T>(initialValue: T): RefObject<T>;
|
|
8
11
|
declare function useRef<T>(initialValue: T | null): RefObject<T | null>;
|
|
9
12
|
declare function useRef<T>(initialValue: T | undefined): RefObject<T | undefined>;
|
|
10
13
|
|
|
11
14
|
declare function useRerender(): () => void;
|
|
12
15
|
|
|
16
|
+
export function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
|
|
17
|
+
export function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
|
|
18
|
+
|
|
13
19
|
declare function useEffect(effect: Effect, deps?: DependencyList): void;
|
|
14
20
|
|
|
15
21
|
declare function useMounted(effect: Effect): void;
|
package/hooks/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { lifecycleEventBus, rerender
|
|
1
|
+
import { batchingRerenderLocker, lifecycleEventBus, rerender as _rerender } from '../core/internal.js';
|
|
2
2
|
import { noop } from '../shared/index.js';
|
|
3
|
+
import { callOrGet } from '../shared/utils.js';
|
|
3
4
|
let currentIndex = 0;
|
|
4
5
|
// In runtime this is a nullable variable.
|
|
5
6
|
let currentElement;
|
|
@@ -9,6 +10,9 @@ lifecycleEventBus.subscribe(event => {
|
|
|
9
10
|
if (currentElement.store?.catchHandlers) {
|
|
10
11
|
currentElement.store.catchHandlers = undefined;
|
|
11
12
|
}
|
|
13
|
+
if (currentElement.store?.effectsHookStates) {
|
|
14
|
+
currentElement.store.effectsHookStates = undefined;
|
|
15
|
+
}
|
|
12
16
|
}
|
|
13
17
|
if (event.type === 'afterRender' || event.type === 'errored') {
|
|
14
18
|
currentElement = null;
|
|
@@ -17,19 +21,19 @@ lifecycleEventBus.subscribe(event => {
|
|
|
17
21
|
if (event.type === 'mounted') {
|
|
18
22
|
const element = event.element;
|
|
19
23
|
if (element.store?.effectsHookStates) {
|
|
20
|
-
|
|
24
|
+
batchingRerenderLocker.lock();
|
|
21
25
|
const effects = element.store.effectsHookStates;
|
|
22
26
|
element.store.effectsHookStates = undefined;
|
|
23
27
|
for (const state of effects) {
|
|
24
28
|
state.cleanup = state.effect() || undefined;
|
|
25
29
|
}
|
|
26
|
-
|
|
30
|
+
batchingRerenderLocker.flush();
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
if (event.type === 'updated') {
|
|
30
34
|
const element = event.element;
|
|
31
35
|
if (element.store?.effectsHookStates) {
|
|
32
|
-
|
|
36
|
+
batchingRerenderLocker.lock();
|
|
33
37
|
const effects = element.store.effectsHookStates;
|
|
34
38
|
element.store.effectsHookStates = undefined;
|
|
35
39
|
for (const state of effects) {
|
|
@@ -38,7 +42,7 @@ lifecycleEventBus.subscribe(event => {
|
|
|
38
42
|
}
|
|
39
43
|
state.cleanup = state.effect() || undefined;
|
|
40
44
|
}
|
|
41
|
-
|
|
45
|
+
batchingRerenderLocker.flush();
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
48
|
if (event.type === 'unmounted') {
|
|
@@ -57,11 +61,11 @@ lifecycleEventBus.subscribe(event => {
|
|
|
57
61
|
throw new Error('Error occurred during rendering a component', { cause: event.error });
|
|
58
62
|
}
|
|
59
63
|
if (element.store.catchHandlers) {
|
|
60
|
-
|
|
64
|
+
batchingRerenderLocker.lock();
|
|
61
65
|
for (const state of element.store.catchHandlers) {
|
|
62
66
|
state(event.error);
|
|
63
67
|
}
|
|
64
|
-
|
|
68
|
+
batchingRerenderLocker.flush();
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
});
|
|
@@ -86,7 +90,26 @@ export function useRerender() {
|
|
|
86
90
|
const hookStates = getOrCreateHookStates(currentElement);
|
|
87
91
|
if (!hookStates[currentIndex]) {
|
|
88
92
|
const elementStore = currentElement.store;
|
|
89
|
-
hookStates[currentIndex] =
|
|
93
|
+
hookStates[currentIndex] = function rerender() {
|
|
94
|
+
_rerender(elementStore.latestElement);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return hookStates[currentIndex++];
|
|
98
|
+
}
|
|
99
|
+
export function useState(initialState) {
|
|
100
|
+
const hookStates = getOrCreateHookStates(currentElement);
|
|
101
|
+
if (!hookStates[currentIndex]) {
|
|
102
|
+
const elementStore = currentElement.store;
|
|
103
|
+
const state = (hookStates[currentIndex] = [undefined, undefined]);
|
|
104
|
+
state[0] = callOrGet(initialState);
|
|
105
|
+
state[1] = function dispatch(action) {
|
|
106
|
+
const nextValue = callOrGet(action, state[0]);
|
|
107
|
+
if (Object.is(state[0], nextValue)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
state[0] = nextValue;
|
|
111
|
+
_rerender(elementStore.latestElement);
|
|
112
|
+
};
|
|
90
113
|
}
|
|
91
114
|
return hookStates[currentIndex++];
|
|
92
115
|
}
|
|
@@ -159,4 +182,14 @@ function getOrCreateEffectHookStates(element) {
|
|
|
159
182
|
}
|
|
160
183
|
return element.store.effectsHookStates;
|
|
161
184
|
}
|
|
162
|
-
export default {
|
|
185
|
+
export default {
|
|
186
|
+
useRef,
|
|
187
|
+
useRerender,
|
|
188
|
+
useState,
|
|
189
|
+
useEffect,
|
|
190
|
+
useMounted,
|
|
191
|
+
useUnmounted,
|
|
192
|
+
useContext,
|
|
193
|
+
useCatch,
|
|
194
|
+
areDepsEqual,
|
|
195
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simpreact/simpreact",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"homepage": "https://github.com/dPaskhin/simpreact#readme",
|
|
6
6
|
"main": "./core/index.js",
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
"import": "./core/index.js",
|
|
12
12
|
"types": "./core/index.d.ts"
|
|
13
13
|
},
|
|
14
|
+
"./compat": {
|
|
15
|
+
"import": "./compat/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./compat/*": {
|
|
18
|
+
"import": "./compat/index.js"
|
|
19
|
+
},
|
|
14
20
|
"./internal": {
|
|
15
21
|
"import": "./core/internal.js",
|
|
16
22
|
"types": "./core/internal.d.ts"
|
package/shared/utils.d.ts
CHANGED
package/shared/utils.js
CHANGED
|
@@ -2,3 +2,19 @@ export function isSimpText(value) {
|
|
|
2
2
|
return typeof value === 'string' || typeof value === 'number' || typeof value === 'bigint';
|
|
3
3
|
}
|
|
4
4
|
export function noop() { }
|
|
5
|
+
export function callOrGet(value) {
|
|
6
|
+
if (typeof value !== 'function') {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
if (arguments.length === 1) {
|
|
10
|
+
return value();
|
|
11
|
+
}
|
|
12
|
+
if (arguments.length === 2) {
|
|
13
|
+
return value(arguments[1]);
|
|
14
|
+
}
|
|
15
|
+
const args = [];
|
|
16
|
+
for (let i = 1; i < arguments.length; ++i) {
|
|
17
|
+
args.push(arguments[i]);
|
|
18
|
+
}
|
|
19
|
+
return value(...args);
|
|
20
|
+
}
|