@simpreact/simpreact 0.0.2 → 0.0.4
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/context.d.ts +8 -0
- package/compat/context.js +7 -0
- package/compat/core.d.ts +43 -0
- package/compat/core.js +90 -0
- package/compat/dom.d.ts +11 -0
- package/compat/dom.js +10 -0
- package/compat/hooks.d.ts +24 -0
- package/compat/hooks.js +75 -0
- package/compat/index.d.ts +43 -0
- package/compat/index.js +17 -0
- package/compat/jsx-runtime.d.ts +10 -0
- package/compat/jsx-runtime.js +9 -0
- package/context/index.d.ts +24 -0
- package/context/index.js +64 -0
- package/core/createElement.d.ts +6 -9
- package/core/createElement.js +4 -25
- package/core/index.d.ts +4 -23
- package/core/index.js +3 -3
- package/core/internal.d.ts +1 -1
- package/core/internal.js +1 -1
- package/core/lifecycleEventBus.d.ts +4 -0
- package/core/memo.d.ts +10 -0
- package/core/memo.js +11 -0
- package/core/mounting.d.ts +6 -9
- package/core/mounting.js +44 -49
- package/core/patching.d.ts +4 -5
- package/core/patching.js +81 -68
- package/core/ref.d.ts +4 -4
- package/core/rerender.d.ts +7 -6
- package/core/rerender.js +91 -32
- package/core/unmounting.js +7 -3
- package/dom/attach-element-to-dom.js +11 -2
- package/dom/events.d.ts +9 -1
- package/dom/events.js +21 -5
- 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 +7 -3
- package/hooks/index.js +46 -14
- package/package.json +7 -1
- package/shared/index.d.ts +6 -0
- package/shared/index.js +12 -3
- package/shared/utils.d.ts +2 -0
- package/shared/utils.js +36 -0
- package/core/context.d.ts +0 -18
- package/core/context.js +0 -18
package/core/rerender.js
CHANGED
|
@@ -1,69 +1,128 @@
|
|
|
1
1
|
import { findParentReferenceFromElement, updateFunctionalComponent } from './patching.js';
|
|
2
|
+
import { lifecycleEventBus } from './lifecycleEventBus.js';
|
|
3
|
+
lifecycleEventBus.subscribe(event => {
|
|
4
|
+
if (event.type === 'afterRender' || event.type === 'errored' || event.type === 'unmounted') {
|
|
5
|
+
batchingRerenderLocker._untrack(event.element.store);
|
|
6
|
+
renderingRerenderLocker._untrack(event.element.store);
|
|
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.store);
|
|
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.store);
|
|
23
|
+
return;
|
|
20
24
|
}
|
|
25
|
+
renderingRerenderLocker.lock();
|
|
26
|
+
updateFunctionalComponent(element, findParentReferenceFromElement(element), null, element.context || null, element.store.hostNamespace);
|
|
27
|
+
renderingRerenderLocker.flush();
|
|
21
28
|
}
|
|
22
|
-
export const
|
|
29
|
+
export const batchingRerenderLocker = {
|
|
23
30
|
_isLocked: false,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
_elementStores: new Set(),
|
|
32
|
+
_last: undefined,
|
|
33
|
+
_track(store) {
|
|
34
|
+
if (this._elementStores.has(store)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (this._elementStores.size === 0 || store.forceRender) {
|
|
38
|
+
this._elementStores.add(store);
|
|
39
|
+
this._last = store;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (isParentOf(store.latestElement, this._last.latestElement)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (isParentOf(this._last.latestElement, store.latestElement)) {
|
|
46
|
+
this._elementStores.clear();
|
|
47
|
+
this._elementStores.add(store);
|
|
48
|
+
this._last = store;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
_untrack(store) {
|
|
52
|
+
if (this._elementStores.delete(store) && store === this._last) {
|
|
53
|
+
this._last = undefined;
|
|
54
|
+
for (const val of this._elementStores) {
|
|
55
|
+
this._last = val;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
27
58
|
},
|
|
28
59
|
lock() {
|
|
29
60
|
this._isLocked = true;
|
|
30
61
|
},
|
|
31
|
-
track(element) {
|
|
32
|
-
this._elements.add(element);
|
|
33
|
-
},
|
|
34
62
|
flush() {
|
|
35
63
|
this._isLocked = false;
|
|
36
|
-
if (this.
|
|
64
|
+
if (this._elementStores.size === 0) {
|
|
37
65
|
return;
|
|
38
66
|
}
|
|
39
|
-
for (const
|
|
40
|
-
this.
|
|
41
|
-
rerender(
|
|
67
|
+
for (const store of this._elementStores) {
|
|
68
|
+
this._untrack(store);
|
|
69
|
+
rerender(store.latestElement);
|
|
42
70
|
}
|
|
43
71
|
},
|
|
44
72
|
};
|
|
45
|
-
export const
|
|
73
|
+
export const renderingRerenderLocker = {
|
|
46
74
|
_isLocked: false,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
75
|
+
_elementStores: new Set(),
|
|
76
|
+
_last: undefined,
|
|
77
|
+
_track(store) {
|
|
78
|
+
if (this._elementStores.has(store)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (this._elementStores.size === 0 || store.forceRender) {
|
|
82
|
+
this._elementStores.add(store);
|
|
83
|
+
this._last = store;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (isParentOf(store.latestElement, this._last.latestElement)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (isParentOf(this._last.latestElement, store.latestElement)) {
|
|
90
|
+
this._elementStores.clear();
|
|
91
|
+
this._elementStores.add(store);
|
|
92
|
+
this._last = store;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
_untrack(store) {
|
|
96
|
+
if (this._elementStores.delete(store) && store === this._last) {
|
|
97
|
+
this._last = undefined;
|
|
98
|
+
for (const val of this._elementStores) {
|
|
99
|
+
this._last = val;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
50
102
|
},
|
|
51
103
|
lock() {
|
|
52
104
|
this._isLocked = true;
|
|
53
105
|
},
|
|
54
|
-
track(element) {
|
|
55
|
-
this._elements.add(element);
|
|
56
|
-
},
|
|
57
106
|
flush() {
|
|
58
107
|
this._isLocked = false;
|
|
59
|
-
if (this.
|
|
108
|
+
if (this._elementStores.size === 0) {
|
|
60
109
|
return;
|
|
61
110
|
}
|
|
62
111
|
queueMicrotask(() => {
|
|
63
|
-
for (const
|
|
64
|
-
this.
|
|
65
|
-
rerender(
|
|
112
|
+
for (const store of this._elementStores) {
|
|
113
|
+
this._untrack(store);
|
|
114
|
+
rerender(store.latestElement);
|
|
66
115
|
}
|
|
67
116
|
});
|
|
68
117
|
},
|
|
69
118
|
};
|
|
119
|
+
function isParentOf(element, parent) {
|
|
120
|
+
let current = element.parent;
|
|
121
|
+
while (current) {
|
|
122
|
+
if (current.store === parent.store) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
current = current.parent;
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
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);
|
|
@@ -24,7 +28,7 @@ export function unmount(element) {
|
|
|
24
28
|
remove(element.children, element.ref);
|
|
25
29
|
return;
|
|
26
30
|
}
|
|
27
|
-
// Only FRAGMENT
|
|
31
|
+
// Only FRAGMENT and HOST elements remain,
|
|
28
32
|
// with Maybe<Many<SimpElement>> children due to normalization.
|
|
29
33
|
if (element.children) {
|
|
30
34
|
unmount(element.children);
|
|
@@ -41,11 +45,11 @@ export function clearElementHostReference(element, parentHostReference) {
|
|
|
41
45
|
return;
|
|
42
46
|
}
|
|
43
47
|
const children = element.children;
|
|
44
|
-
if (element.flag === 'FC'
|
|
48
|
+
if (element.flag === 'FC') {
|
|
45
49
|
element = children;
|
|
46
50
|
continue;
|
|
47
51
|
}
|
|
48
|
-
if (element.flag === 'FRAGMENT'
|
|
52
|
+
if (element.flag === 'FRAGMENT') {
|
|
49
53
|
if (Array.isArray(children)) {
|
|
50
54
|
for (let i = 0, len = children.length; i < len; ++i) {
|
|
51
55
|
clearElementHostReference(children[i], parentHostReference);
|
|
@@ -1,9 +1,18 @@
|
|
|
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
|
}
|
|
7
7
|
export function getElementFromDom(target) {
|
|
8
|
-
|
|
8
|
+
if (!target) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
while (target && !(elementPropertyName in target)) {
|
|
12
|
+
target = target.parentElement;
|
|
13
|
+
}
|
|
14
|
+
if (!target) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return target[elementPropertyName];
|
|
9
18
|
}
|
package/dom/events.d.ts
CHANGED
|
@@ -4,12 +4,20 @@ export declare class SyntheticEvent {
|
|
|
4
4
|
nativeEvent: Event;
|
|
5
5
|
currentTarget: Nullable<EventTarget>;
|
|
6
6
|
isPropagationStopped: boolean;
|
|
7
|
-
|
|
7
|
+
_isDefaultPrevented: boolean;
|
|
8
|
+
button?: number;
|
|
9
|
+
buttons?: number;
|
|
10
|
+
pointerId?: number;
|
|
11
|
+
altKey?: boolean;
|
|
12
|
+
ctrlKey?: boolean;
|
|
13
|
+
shiftKey?: boolean;
|
|
14
|
+
metaKey?: boolean;
|
|
8
15
|
constructor(event: Event);
|
|
9
16
|
get target(): EventTarget | null;
|
|
10
17
|
get type(): string;
|
|
11
18
|
stopPropagation(): void;
|
|
12
19
|
preventDefault(): void;
|
|
20
|
+
isDefaultPrevented(): boolean;
|
|
13
21
|
}
|
|
14
22
|
export declare function dispatchDelegatedEvent(event: Event): void;
|
|
15
23
|
export declare function patchEvent(name: string, prevValue: any, nextValue: any, dom: Element): void;
|
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',
|
|
@@ -39,9 +39,22 @@ export class SyntheticEvent {
|
|
|
39
39
|
nativeEvent;
|
|
40
40
|
currentTarget = null;
|
|
41
41
|
isPropagationStopped = false;
|
|
42
|
-
|
|
42
|
+
_isDefaultPrevented = false;
|
|
43
|
+
button;
|
|
44
|
+
buttons;
|
|
45
|
+
pointerId;
|
|
46
|
+
altKey;
|
|
47
|
+
ctrlKey;
|
|
48
|
+
shiftKey;
|
|
49
|
+
metaKey;
|
|
43
50
|
constructor(event) {
|
|
44
51
|
this.nativeEvent = event;
|
|
52
|
+
this.button = event.button;
|
|
53
|
+
this.buttons = event.buttons;
|
|
54
|
+
this.pointerId = event.pointerId;
|
|
55
|
+
this.altKey = event.altKey;
|
|
56
|
+
this.ctrlKey = event.ctrlKey;
|
|
57
|
+
this.metaKey = event.metaKey;
|
|
45
58
|
}
|
|
46
59
|
get target() {
|
|
47
60
|
return this.nativeEvent.target;
|
|
@@ -54,12 +67,15 @@ export class SyntheticEvent {
|
|
|
54
67
|
this.nativeEvent.stopPropagation();
|
|
55
68
|
}
|
|
56
69
|
preventDefault() {
|
|
57
|
-
this.
|
|
70
|
+
this._isDefaultPrevented = true;
|
|
58
71
|
this.nativeEvent.preventDefault();
|
|
59
72
|
}
|
|
73
|
+
isDefaultPrevented() {
|
|
74
|
+
return this._isDefaultPrevented;
|
|
75
|
+
}
|
|
60
76
|
}
|
|
61
77
|
export function dispatchDelegatedEvent(event) {
|
|
62
|
-
|
|
78
|
+
batchingRerenderLocker.lock();
|
|
63
79
|
const syntheticEvent = new SyntheticEvent(event);
|
|
64
80
|
const captureHandlers = [];
|
|
65
81
|
const bubbleHandlers = [];
|
|
@@ -91,7 +107,7 @@ export function dispatchDelegatedEvent(event) {
|
|
|
91
107
|
return;
|
|
92
108
|
}
|
|
93
109
|
}
|
|
94
|
-
|
|
110
|
+
batchingRerenderLocker.flush();
|
|
95
111
|
}
|
|
96
112
|
const captureRegex = /Capture/;
|
|
97
113
|
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
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import type { RefObject
|
|
1
|
+
import type { RefObject } from '../core/index.js';
|
|
2
2
|
|
|
3
3
|
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;
|
|
16
22
|
|
|
17
23
|
declare function useUnmounted(cleanup: Cleanup): void;
|
|
18
24
|
|
|
19
|
-
declare function useContext<T>(context: SimpContext<T>): T;
|
|
20
|
-
|
|
21
25
|
declare function useCatch(cb: (error: any) => void): void;
|
|
22
26
|
|
|
23
27
|
declare function areDepsEqual(nextDeps: DependencyList | undefined, prevDeps: DependencyList | undefined): boolean;
|
package/hooks/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { lifecycleEventBus, rerender
|
|
2
|
-
import { noop } from '../shared/index.js';
|
|
1
|
+
import { batchingRerenderLocker, lifecycleEventBus, rerender as _rerender } from '../core/internal.js';
|
|
2
|
+
import { callOrGet, noop } from '../shared/index.js';
|
|
3
3
|
let currentIndex = 0;
|
|
4
4
|
// In runtime this is a nullable variable.
|
|
5
5
|
let currentElement;
|
|
@@ -9,6 +9,9 @@ lifecycleEventBus.subscribe(event => {
|
|
|
9
9
|
if (currentElement.store?.catchHandlers) {
|
|
10
10
|
currentElement.store.catchHandlers = undefined;
|
|
11
11
|
}
|
|
12
|
+
if (currentElement.store?.effectsHookStates) {
|
|
13
|
+
currentElement.store.effectsHookStates = undefined;
|
|
14
|
+
}
|
|
12
15
|
}
|
|
13
16
|
if (event.type === 'afterRender' || event.type === 'errored') {
|
|
14
17
|
currentElement = null;
|
|
@@ -17,19 +20,19 @@ lifecycleEventBus.subscribe(event => {
|
|
|
17
20
|
if (event.type === 'mounted') {
|
|
18
21
|
const element = event.element;
|
|
19
22
|
if (element.store?.effectsHookStates) {
|
|
20
|
-
|
|
23
|
+
batchingRerenderLocker.lock();
|
|
21
24
|
const effects = element.store.effectsHookStates;
|
|
22
25
|
element.store.effectsHookStates = undefined;
|
|
23
26
|
for (const state of effects) {
|
|
24
27
|
state.cleanup = state.effect() || undefined;
|
|
25
28
|
}
|
|
26
|
-
|
|
29
|
+
batchingRerenderLocker.flush();
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
if (event.type === 'updated') {
|
|
30
33
|
const element = event.element;
|
|
31
34
|
if (element.store?.effectsHookStates) {
|
|
32
|
-
|
|
35
|
+
batchingRerenderLocker.lock();
|
|
33
36
|
const effects = element.store.effectsHookStates;
|
|
34
37
|
element.store.effectsHookStates = undefined;
|
|
35
38
|
for (const state of effects) {
|
|
@@ -38,13 +41,15 @@ lifecycleEventBus.subscribe(event => {
|
|
|
38
41
|
}
|
|
39
42
|
state.cleanup = state.effect() || undefined;
|
|
40
43
|
}
|
|
41
|
-
|
|
44
|
+
batchingRerenderLocker.flush();
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
if (event.type === 'unmounted') {
|
|
45
48
|
const element = event.element;
|
|
46
49
|
if (element.store?.hookStates) {
|
|
47
|
-
|
|
50
|
+
const hookStates = element.store.hookStates;
|
|
51
|
+
element.store.hookStates = undefined;
|
|
52
|
+
for (const state of hookStates) {
|
|
48
53
|
if (state && 'cleanup' in state && typeof state.cleanup === 'function') {
|
|
49
54
|
state.cleanup();
|
|
50
55
|
}
|
|
@@ -57,11 +62,11 @@ lifecycleEventBus.subscribe(event => {
|
|
|
57
62
|
throw new Error('Error occurred during rendering a component', { cause: event.error });
|
|
58
63
|
}
|
|
59
64
|
if (element.store.catchHandlers) {
|
|
60
|
-
|
|
65
|
+
batchingRerenderLocker.lock();
|
|
61
66
|
for (const state of element.store.catchHandlers) {
|
|
62
67
|
state(event.error);
|
|
63
68
|
}
|
|
64
|
-
|
|
69
|
+
batchingRerenderLocker.flush();
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
72
|
});
|
|
@@ -86,7 +91,28 @@ export function useRerender() {
|
|
|
86
91
|
const hookStates = getOrCreateHookStates(currentElement);
|
|
87
92
|
if (!hookStates[currentIndex]) {
|
|
88
93
|
const elementStore = currentElement.store;
|
|
89
|
-
hookStates[currentIndex] =
|
|
94
|
+
hookStates[currentIndex] = function rerender() {
|
|
95
|
+
elementStore.forceRender = true;
|
|
96
|
+
_rerender(elementStore.latestElement);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return hookStates[currentIndex++];
|
|
100
|
+
}
|
|
101
|
+
export function useState(initialState) {
|
|
102
|
+
const hookStates = getOrCreateHookStates(currentElement);
|
|
103
|
+
if (!hookStates[currentIndex]) {
|
|
104
|
+
const elementStore = currentElement.store;
|
|
105
|
+
const state = (hookStates[currentIndex] = [undefined, undefined]);
|
|
106
|
+
state[0] = callOrGet(initialState);
|
|
107
|
+
state[1] = function dispatch(action) {
|
|
108
|
+
const nextValue = callOrGet(action, state[0]);
|
|
109
|
+
if (Object.is(state[0], nextValue)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
state[0] = nextValue;
|
|
113
|
+
elementStore.forceRender = true;
|
|
114
|
+
_rerender(elementStore.latestElement);
|
|
115
|
+
};
|
|
90
116
|
}
|
|
91
117
|
return hookStates[currentIndex++];
|
|
92
118
|
}
|
|
@@ -118,9 +144,6 @@ export function useUnmounted(cleanup) {
|
|
|
118
144
|
}
|
|
119
145
|
currentIndex++;
|
|
120
146
|
}
|
|
121
|
-
export function useContext(context) {
|
|
122
|
-
return currentElement.contextMap?.get(context) ?? context.defaultValue;
|
|
123
|
-
}
|
|
124
147
|
export function useCatch(cb) {
|
|
125
148
|
if (!currentElement.store) {
|
|
126
149
|
currentElement.store = {};
|
|
@@ -159,4 +182,13 @@ 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
|
+
useCatch,
|
|
193
|
+
areDepsEqual,
|
|
194
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simpreact/simpreact",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
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/index.d.ts
CHANGED
|
@@ -17,3 +17,9 @@ declare class EventBus<Event = void> {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
declare function isSimpText(value: unknown): value is SimpText;
|
|
20
|
+
|
|
21
|
+
declare function noop(): void;
|
|
22
|
+
|
|
23
|
+
declare function callOrGet<T, A extends any[]>(value: T | ((...args: A) => T), ...args: A): T;
|
|
24
|
+
|
|
25
|
+
declare function shallowEqual(objA: any, objB: any): boolean;
|
package/shared/index.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { EventBus } from './EventBus.js';
|
|
2
2
|
import { emptyArray, emptyMap, emptyObject } from './lang.js';
|
|
3
|
-
import { isSimpText, noop } from './utils.js';
|
|
4
|
-
export { emptyObject, emptyMap, emptyArray, isSimpText, EventBus, noop };
|
|
5
|
-
export default {
|
|
3
|
+
import { callOrGet, isSimpText, noop, shallowEqual } from './utils.js';
|
|
4
|
+
export { emptyObject, emptyMap, emptyArray, isSimpText, EventBus, noop, callOrGet, shallowEqual };
|
|
5
|
+
export default {
|
|
6
|
+
isSimpText,
|
|
7
|
+
EMPTY_MAP: emptyMap,
|
|
8
|
+
EMPTY_ARRAY: emptyArray,
|
|
9
|
+
EMPTY_OBJECT: emptyObject,
|
|
10
|
+
EventBus,
|
|
11
|
+
noop,
|
|
12
|
+
callOrGet,
|
|
13
|
+
emptyObject,
|
|
14
|
+
};
|
package/shared/utils.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { SimpText } from './public.js';
|
|
2
2
|
export declare function isSimpText(value: unknown): value is SimpText;
|
|
3
3
|
export declare function noop(): void;
|
|
4
|
+
export declare function callOrGet<T, A extends any[]>(value: T | ((...args: A) => T), ...args: A): T;
|
|
5
|
+
export declare function shallowEqual(objA: any, objB: any): boolean;
|