@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.
Files changed (48) hide show
  1. package/compat/context.d.ts +8 -0
  2. package/compat/context.js +7 -0
  3. package/compat/core.d.ts +43 -0
  4. package/compat/core.js +90 -0
  5. package/compat/dom.d.ts +11 -0
  6. package/compat/dom.js +10 -0
  7. package/compat/hooks.d.ts +24 -0
  8. package/compat/hooks.js +75 -0
  9. package/compat/index.d.ts +43 -0
  10. package/compat/index.js +17 -0
  11. package/compat/jsx-runtime.d.ts +10 -0
  12. package/compat/jsx-runtime.js +9 -0
  13. package/context/index.d.ts +24 -0
  14. package/context/index.js +64 -0
  15. package/core/createElement.d.ts +6 -9
  16. package/core/createElement.js +4 -25
  17. package/core/index.d.ts +4 -23
  18. package/core/index.js +3 -3
  19. package/core/internal.d.ts +1 -1
  20. package/core/internal.js +1 -1
  21. package/core/lifecycleEventBus.d.ts +4 -0
  22. package/core/memo.d.ts +10 -0
  23. package/core/memo.js +11 -0
  24. package/core/mounting.d.ts +6 -9
  25. package/core/mounting.js +44 -49
  26. package/core/patching.d.ts +4 -5
  27. package/core/patching.js +81 -68
  28. package/core/ref.d.ts +4 -4
  29. package/core/rerender.d.ts +7 -6
  30. package/core/rerender.js +91 -32
  31. package/core/unmounting.js +7 -3
  32. package/dom/attach-element-to-dom.js +11 -2
  33. package/dom/events.d.ts +9 -1
  34. package/dom/events.js +21 -5
  35. package/dom/props/controlled/input.js +5 -5
  36. package/dom/props/controlled/select.js +3 -3
  37. package/dom/props/controlled/textarea.js +5 -5
  38. package/dom/props/style.js +6 -3
  39. package/dom/render.js +3 -3
  40. package/hooks/index.d.ts +7 -3
  41. package/hooks/index.js +46 -14
  42. package/package.json +7 -1
  43. package/shared/index.d.ts +6 -0
  44. package/shared/index.js +12 -3
  45. package/shared/utils.d.ts +2 -0
  46. package/shared/utils.js +36 -0
  47. package/core/context.d.ts +0 -18
  48. 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
- if (syncRerenderLocker.isLocked) {
10
- syncRerenderLocker.track(element);
16
+ lifecycleEventBus.publish({ type: 'triedToRerender', element });
17
+ if (batchingRerenderLocker._isLocked) {
18
+ batchingRerenderLocker._track(element.store);
11
19
  return;
12
20
  }
13
- else if (asyncRerenderLocker.isLocked) {
14
- asyncRerenderLocker.track(element);
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 syncRerenderLocker = {
29
+ export const batchingRerenderLocker = {
23
30
  _isLocked: false,
24
- _elements: new Set(),
25
- get isLocked() {
26
- return this._isLocked;
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._elements.size === 0) {
64
+ if (this._elementStores.size === 0) {
37
65
  return;
38
66
  }
39
- for (const element of this._elements) {
40
- this._elements.delete(element);
41
- rerender(element.store.latestElement);
67
+ for (const store of this._elementStores) {
68
+ this._untrack(store);
69
+ rerender(store.latestElement);
42
70
  }
43
71
  },
44
72
  };
45
- export const asyncRerenderLocker = {
73
+ export const renderingRerenderLocker = {
46
74
  _isLocked: false,
47
- _elements: new Set(),
48
- get isLocked() {
49
- return this._isLocked;
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._elements.size === 0) {
108
+ if (this._elementStores.size === 0) {
60
109
  return;
61
110
  }
62
111
  queueMicrotask(() => {
63
- for (const element of this._elements) {
64
- this._elements.delete(element);
65
- rerender(element.store.latestElement);
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
+ }
@@ -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, PROVIDER, CONSUMER, and HOST elements remain,
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' || element.flag === 'CONSUMER') {
48
+ if (element.flag === 'FC') {
45
49
  element = children;
46
50
  continue;
47
51
  }
48
- if (element.flag === 'FRAGMENT' || element.flag === 'PROVIDER') {
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 (dom.nodeType !== Node.TEXT_NODE) {
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
- return target?.[elementPropertyName] ?? null;
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
- isDefaultPrevented: boolean;
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 { syncRerenderLocker } from '../core/internal.js';
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
- isDefaultPrevented = false;
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.isDefaultPrevented = true;
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
- syncRerenderLocker.lock();
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
- syncRerenderLocker.flush();
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 { syncRerenderLocker } from '../../../core/internal.js';
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
- syncRerenderLocker.lock();
15
+ batchingRerenderLocker.lock();
16
16
  element.props['onInput'](event);
17
- syncRerenderLocker.flush();
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
- syncRerenderLocker.lock();
30
+ batchingRerenderLocker.lock();
31
31
  element.props['onChange'](event);
32
- syncRerenderLocker.flush();
32
+ batchingRerenderLocker.flush();
33
33
  element = getElementFromDom(event.target);
34
34
  }
35
35
  if (element) {
@@ -1,4 +1,4 @@
1
- import { syncRerenderLocker } from '../../../core/internal.js';
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
- syncRerenderLocker.lock();
13
+ batchingRerenderLocker.lock();
14
14
  element.props['onChange'](event);
15
- syncRerenderLocker.flush();
15
+ batchingRerenderLocker.flush();
16
16
  element = getElementFromDom(event.target);
17
17
  }
18
18
  if (element) {
@@ -1,4 +1,4 @@
1
- import { syncRerenderLocker } from '../../../core/internal.js';
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
- syncRerenderLocker.lock();
12
+ batchingRerenderLocker.lock();
13
13
  element.props['onChange'](event);
14
- syncRerenderLocker.flush();
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
- syncRerenderLocker.lock();
27
+ batchingRerenderLocker.lock();
28
28
  element.props['onInput'](event);
29
- syncRerenderLocker.flush();
29
+ batchingRerenderLocker.flush();
30
30
  element = getElementFromDom(event.target);
31
31
  }
32
32
  if (element) {
@@ -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 { asyncRerenderLocker, hostAdapter, mount, patch, provideHostAdapter, remove, } from '../core/internal.js';
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
- asyncRerenderLocker.lock();
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
- asyncRerenderLocker.flush();
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, SimpContext } from '../core/index.js';
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, syncRerenderLocker } from '../core/internal.js';
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
- syncRerenderLocker.lock();
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
- syncRerenderLocker.flush();
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
- syncRerenderLocker.lock();
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
- syncRerenderLocker.flush();
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
- for (const state of element.store.hookStates) {
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
- syncRerenderLocker.lock();
65
+ batchingRerenderLocker.lock();
61
66
  for (const state of element.store.catchHandlers) {
62
67
  state(event.error);
63
68
  }
64
- syncRerenderLocker.flush();
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] = () => rerender(elementStore.latestElement);
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 { useRef, useRerender, useEffect, useMounted, useUnmounted, useContext, useCatch, areDepsEqual };
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.2",
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 { isSimpText, EMPTY_MAP: emptyMap, EMPTY_ARRAY: emptyArray, EMPTY_OBJECT: emptyObject, EventBus, noop };
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;