@simpreact/simpreact 0.0.1 → 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.
Files changed (65) hide show
  1. package/compat/core.d.ts +46 -0
  2. package/compat/core.js +93 -0
  3. package/compat/dom.d.ts +11 -0
  4. package/compat/dom.js +10 -0
  5. package/compat/hooks.d.ts +26 -0
  6. package/compat/hooks.js +77 -0
  7. package/compat/index.d.ts +42 -0
  8. package/compat/index.js +14 -0
  9. package/compat/jsx-runtime.d.ts +10 -0
  10. package/compat/jsx-runtime.js +9 -0
  11. package/compat/utils.d.ts +1 -0
  12. package/compat/utils.js +3 -0
  13. package/core/context.d.ts +1 -1
  14. package/core/createElement.d.ts +4 -4
  15. package/core/createElement.js +13 -6
  16. package/core/fragment.d.ts +1 -1
  17. package/core/hostAdapter.d.ts +3 -3
  18. package/core/index.d.ts +3 -3
  19. package/core/index.js +4 -4
  20. package/core/internal.d.ts +11 -11
  21. package/core/internal.js +11 -11
  22. package/core/lifecycleEventBus.d.ts +6 -2
  23. package/core/lifecycleEventBus.js +1 -1
  24. package/core/mounting.d.ts +4 -4
  25. package/core/mounting.js +42 -11
  26. package/core/patching.d.ts +4 -4
  27. package/core/patching.js +89 -19
  28. package/core/portal.d.ts +1 -1
  29. package/core/portal.js +1 -1
  30. package/core/ref.d.ts +5 -5
  31. package/core/rerender.d.ts +5 -5
  32. package/core/rerender.js +38 -25
  33. package/core/unmounting.d.ts +3 -3
  34. package/core/unmounting.js +7 -3
  35. package/dom/attach-element-to-dom.d.ts +1 -1
  36. package/dom/attach-element-to-dom.js +1 -1
  37. package/dom/domAdapter.d.ts +1 -1
  38. package/dom/domAdapter.js +10 -5
  39. package/dom/events.d.ts +4 -1
  40. package/dom/events.js +10 -4
  41. package/dom/index.d.ts +2 -2
  42. package/dom/index.js +2 -2
  43. package/dom/props/controlled/index.d.ts +1 -1
  44. package/dom/props/controlled/index.js +3 -3
  45. package/dom/props/controlled/input.d.ts +1 -1
  46. package/dom/props/controlled/input.js +6 -6
  47. package/dom/props/controlled/select.d.ts +1 -1
  48. package/dom/props/controlled/select.js +5 -5
  49. package/dom/props/controlled/textarea.d.ts +1 -1
  50. package/dom/props/controlled/textarea.js +6 -6
  51. package/dom/props/dangerInnerHTML.d.ts +1 -1
  52. package/dom/props/index.d.ts +1 -1
  53. package/dom/props/index.js +1 -1
  54. package/dom/props/props.d.ts +1 -1
  55. package/dom/props/props.js +6 -6
  56. package/dom/props/style.js +6 -3
  57. package/dom/render.d.ts +1 -1
  58. package/dom/render.js +5 -5
  59. package/hooks/index.d.ts +7 -1
  60. package/hooks/index.js +43 -10
  61. package/jsx-runtime/index.d.ts +3 -3
  62. package/package.json +8 -1
  63. package/shared/index.js +3 -3
  64. package/shared/utils.d.ts +2 -1
  65. package/shared/utils.js +16 -0
package/core/mounting.js CHANGED
@@ -1,8 +1,9 @@
1
- import { emptyMap, emptyObject } from '../shared';
2
- import { hostAdapter } from './hostAdapter';
3
- import { createTextElement, normalizeRoot } from './createElement';
4
- import { applyRef } from './ref';
5
- import { lifecycleEventBus } from './lifecycleEventBus';
1
+ import { emptyMap, emptyObject } from '../shared/index.js';
2
+ import { hostAdapter } from './hostAdapter.js';
3
+ import { createTextElement, normalizeRoot } from './createElement.js';
4
+ import { applyRef } from './ref.js';
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);
@@ -37,9 +38,6 @@ export function mountHostElement(element, parentReference, nextReference, contex
37
38
  hostNamespace = hostNamespaces?.self;
38
39
  const hostReference = (element.reference = hostAdapter.createReference(element.type, hostNamespace));
39
40
  hostAdapter.attachElementToReference(element, hostReference);
40
- if (parentReference) {
41
- hostAdapter.insertOrAppend(parentReference, hostReference, nextReference);
42
- }
43
41
  // HOST element always has Maybe<Many<SimpElement>> children due to normalization process.
44
42
  const children = element.children;
45
43
  if (Array.isArray(children)) {
@@ -51,10 +49,17 @@ export function mountHostElement(element, parentReference, nextReference, contex
51
49
  }
52
50
  if (element.props) {
53
51
  hostAdapter.mountProps(hostReference, element, hostNamespace);
52
+ // HOST elements can have either children elements or string children in the props due to normalization.
53
+ if (typeof element.props.children === 'string') {
54
+ hostAdapter.setTextContent(hostReference, element.props.children);
55
+ }
54
56
  }
55
57
  if (element.className) {
56
58
  hostAdapter.setClassname(hostReference, element.className, hostNamespace);
57
59
  }
60
+ if (parentReference) {
61
+ hostAdapter.insertOrAppend(parentReference, hostReference, nextReference);
62
+ }
58
63
  applyRef(element);
59
64
  }
60
65
  export function mountFunctionalElement(element, parentReference, nextReference, contextMap, hostNamespace) {
@@ -70,15 +75,41 @@ export function mountFunctionalElement(element, parentReference, nextReference,
70
75
  }
71
76
  // FC element always has Maybe<SimpElement> children due to normalization process.
72
77
  let children;
78
+ let triedToRerenderUnsubscribe;
73
79
  try {
74
- lifecycleEventBus.publish({ type: 'beforeRender', element, phase: 'mounting' });
75
- children = normalizeRoot(element.type(element.props || emptyObject), false);
76
- lifecycleEventBus.publish({ type: 'afterRender', phase: 'mounting' });
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);
77
105
  }
78
106
  catch (error) {
79
107
  lifecycleEventBus.publish({ type: 'errored', element, error, phase: 'mounting' });
80
108
  return;
81
109
  }
110
+ finally {
111
+ triedToRerenderUnsubscribe();
112
+ }
82
113
  if (children) {
83
114
  children.parent = element;
84
115
  mount((element.children = children), parentReference, nextReference, contextMap, hostNamespace);
@@ -1,7 +1,7 @@
1
- import type { Maybe, Nullable } from '../shared';
2
- import type { SimpElement } from './createElement';
3
- import type { HostReference } from './hostAdapter';
4
- import type { SimpContextMap } from './context';
1
+ import type { Maybe, Nullable } from '../shared/index.js';
2
+ import type { SimpElement } from './createElement.js';
3
+ import type { HostReference } from './hostAdapter.js';
4
+ import type { SimpContextMap } from './context.js';
5
5
  export declare function patch(prevElement: SimpElement, nextElement: SimpElement, parentReference: HostReference, nextReference: Nullable<HostReference>, contextMap: Nullable<SimpContextMap>, hostNamespace: Maybe<string>): void;
6
6
  export declare function patchPortal(prevElement: SimpElement, nextElement: SimpElement, contextMap: Nullable<SimpContextMap>): void;
7
7
  export declare function updateFunctionalComponent(element: SimpElement, parentReference: HostReference, nextReference: Nullable<HostReference>, contextMap: Nullable<SimpContextMap>, hostNamespace: Maybe<string>): void;
package/core/patching.js CHANGED
@@ -1,10 +1,11 @@
1
- import { emptyMap, emptyObject } from '../shared';
2
- import { normalizeRoot } from './createElement';
3
- import { hostAdapter } from './hostAdapter';
4
- import { clearElementHostReference, remove, unmount } from './unmounting';
5
- import { mount, mountArrayChildren } from './mounting';
6
- import { applyRef } from './ref';
7
- import { lifecycleEventBus } from './lifecycleEventBus';
1
+ import { emptyMap, emptyObject } from '../shared/index.js';
2
+ import { normalizeRoot } from './createElement.js';
3
+ import { hostAdapter } from './hostAdapter.js';
4
+ import { clearElementHostReference, remove, unmount } from './unmounting.js';
5
+ import { mount, mountArrayChildren } from './mounting.js';
6
+ import { applyRef } from './ref.js';
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);
@@ -51,7 +52,7 @@ function patchHostElement(prevElement, nextElement, contextMap, hostNamespace) {
51
52
  hostNamespace = hostNamespaces?.self;
52
53
  nextElement.reference = prevElement.reference;
53
54
  hostAdapter.attachElementToReference(nextElement, nextElement.reference);
54
- patchChildren(prevElement.children, nextElement.children, nextElement.reference, null, nextElement, contextMap, hostNamespaces?.children);
55
+ patchChildren(prevElement.children || prevElement.props?.children, nextElement.children || nextElement.props?.children, nextElement.reference, null, nextElement, contextMap, hostNamespaces?.children);
55
56
  hostAdapter.patchProps(nextElement.reference, prevElement, nextElement, hostNamespace);
56
57
  if (prevElement.className !== nextElement.className) {
57
58
  hostAdapter.setClassname(nextElement.reference, nextElement.className, 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
- (nextElement.store = prevElement.store ||= {}).latestElement = nextElement;
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
- lifecycleEventBus.publish({ type: 'beforeRender', element: nextElement, phase: 'updating' });
72
- nextChildren = normalizeRoot(nextElement.type(nextElement.props || emptyObject), false);
73
- lifecycleEventBus.publish({ type: 'afterRender', phase: 'updating' });
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
- patchChildren(prevChildren, nextChildren, parentReference, nextReference, nextElement, contextMap, hostNamespace);
84
- lifecycleEventBus.publish({ type: 'updated', element: nextElement });
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;
@@ -134,6 +175,10 @@ function patchChildren(prevChildren, nextChildren, parentReference, nextReferenc
134
175
  }
135
176
  patchKeyedChildren(prevChildren, nextChildren, parentReference, nextReference, contextMap, hostNamespace);
136
177
  }
178
+ else if (typeof nextChildren === 'string') {
179
+ unmount(prevChildren);
180
+ hostAdapter.setTextContent(parentReference, nextChildren);
181
+ }
137
182
  else if (nextChildren) {
138
183
  patchKeyedChildren(prevChildren, [nextChildren], parentReference, nextReference, contextMap, hostNamespace);
139
184
  }
@@ -142,23 +187,48 @@ function patchChildren(prevChildren, nextChildren, parentReference, nextReferenc
142
187
  hostAdapter.clearNode(parentReference);
143
188
  }
144
189
  }
190
+ else if (typeof prevChildren === 'string') {
191
+ if (Array.isArray(nextChildren)) {
192
+ hostAdapter.clearNode(parentReference);
193
+ mountArrayChildren(nextChildren, parentReference, nextReference, contextMap, nextElement, hostNamespace);
194
+ }
195
+ else if (typeof nextChildren === 'string') {
196
+ if (prevChildren !== nextChildren) {
197
+ hostAdapter.setTextContent(nextElement.reference, nextChildren, true);
198
+ }
199
+ }
200
+ else if (nextChildren) {
201
+ hostAdapter.clearNode(parentReference);
202
+ nextChildren.parent = nextElement;
203
+ mount(nextChildren, parentReference, nextReference, contextMap, hostNamespace);
204
+ }
205
+ else {
206
+ hostAdapter.clearNode(parentReference);
207
+ }
208
+ }
145
209
  else if (prevChildren) {
146
210
  if (Array.isArray(nextChildren)) {
147
211
  patchKeyedChildren([prevChildren], nextChildren, parentReference, nextReference, contextMap, hostNamespace);
148
212
  }
213
+ else if (typeof nextChildren === 'string') {
214
+ unmount(prevChildren);
215
+ hostAdapter.setTextContent(parentReference, nextChildren);
216
+ }
149
217
  else if (nextChildren) {
150
218
  nextChildren.parent = nextElement;
151
219
  patch(prevChildren, nextChildren, parentReference, nextReference, contextMap, hostNamespace);
152
220
  }
153
221
  else {
154
- unmount(prevChildren);
155
- hostAdapter.clearNode(parentReference);
222
+ remove(prevChildren, parentReference);
156
223
  }
157
224
  }
158
225
  else {
159
226
  if (Array.isArray(nextChildren)) {
160
227
  mountArrayChildren(nextChildren, parentReference, nextReference, contextMap, nextElement, hostNamespace);
161
228
  }
229
+ else if (typeof nextChildren === 'string') {
230
+ hostAdapter.setTextContent(parentReference, nextChildren);
231
+ }
162
232
  else if (nextChildren) {
163
233
  nextChildren.parent = nextElement;
164
234
  mount(nextChildren, parentReference, nextReference, contextMap, hostNamespace);
@@ -186,7 +256,7 @@ export function patchKeyedChildren(prevChildren, nextChildren, parentReference,
186
256
  }
187
257
  // Step 3: Mount new nodes if prev list is exhausted
188
258
  if (prevStart > prevEnd) {
189
- const before = nextChildren[nextEnd + 1]?.reference || nextReference;
259
+ const before = findHostReferenceFromElement(nextChildren[nextEnd + 1]) || nextReference;
190
260
  for (let i = nextStart; i <= nextEnd; i++) {
191
261
  mount(nextChildren[i], parentReference, before, contextMap, hostNamespace);
192
262
  }
@@ -221,7 +291,7 @@ export function patchKeyedChildren(prevChildren, nextChildren, parentReference,
221
291
  usedIndices.add(prevIndex);
222
292
  }
223
293
  else {
224
- mount(nextChild, parentReference, nextChildren[i + 1]?.reference || nextReference, contextMap, hostNamespace);
294
+ mount(nextChild, parentReference, findHostReferenceFromElement(nextChildren[i + 1]) || nextReference, contextMap, hostNamespace);
225
295
  toMove[i - nextStart] = -1;
226
296
  }
227
297
  }
@@ -234,7 +304,7 @@ export function patchKeyedChildren(prevChildren, nextChildren, parentReference,
234
304
  // Insert in correct order
235
305
  for (let i = nextEnd; i >= nextStart; i--) {
236
306
  const currentChild = nextChildren[i];
237
- const reference = nextChildren[i + 1]?.reference || nextReference;
307
+ const reference = findHostReferenceFromElement(nextChildren[i + 1]) || nextReference;
238
308
  if (toMove[i - nextStart] !== -1) {
239
309
  hostAdapter.insertBefore(parentReference, currentChild.reference, reference);
240
310
  }
package/core/portal.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import type { SimpElement, SimpNode } from './createElement';
1
+ import type { SimpElement, SimpNode } from './createElement.js';
2
2
  export declare function createPortal(children: SimpNode, container: any): SimpElement;
package/core/portal.js CHANGED
@@ -1,4 +1,4 @@
1
- import { normalizeRoot } from './createElement';
1
+ import { normalizeRoot } from './createElement.js';
2
2
  export function createPortal(children, container) {
3
3
  const element = { flag: 'PORTAL', parent: null };
4
4
  if ((children = normalizeRoot(children, false))) {
package/core/ref.d.ts CHANGED
@@ -1,17 +1,17 @@
1
- import type { SimpElement } from './createElement';
1
+ import type { SimpElement } from './createElement.js';
2
2
  interface RefSimpElement extends SimpElement {
3
3
  ref?: {
4
4
  value: NonNullable<Ref<unknown>>;
5
5
  cleanup?: () => void;
6
6
  };
7
7
  }
8
- export interface RefObject<T> {
8
+ export type RefObject<T> = {
9
9
  current: T;
10
- }
10
+ };
11
11
  export type RefCallback<T> = {
12
- bivarianceHack(instance: T | null): (() => void) | void;
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 {};
@@ -1,13 +1,13 @@
1
- import type { SimpElement } from './createElement';
1
+ import type { SimpElement } from './createElement.js';
2
2
  export declare function rerender(element: SimpElement): void;
3
3
  interface IRendererLocker {
4
4
  _isLocked: boolean;
5
5
  _elements: Set<SimpElement>;
6
- isLocked: boolean;
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 syncRerenderLocker: IRendererLocker;
12
- export declare const asyncRerenderLocker: IRendererLocker;
11
+ export declare const batchingRerenderLocker: IRendererLocker;
12
+ export declare const renderingRerenderLocker: IRendererLocker;
13
13
  export {};
package/core/rerender.js CHANGED
@@ -1,61 +1,74 @@
1
- import { findParentReferenceFromElement, updateFunctionalComponent } from './patching';
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
- if (syncRerenderLocker.isLocked) {
10
- syncRerenderLocker.track(element);
16
+ lifecycleEventBus.publish({ type: 'triedToRerender', element });
17
+ if (batchingRerenderLocker._isLocked) {
18
+ batchingRerenderLocker._track(element);
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);
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 syncRerenderLocker = {
29
+ export const batchingRerenderLocker = {
23
30
  _isLocked: false,
24
31
  _elements: new Set(),
25
- get isLocked() {
26
- return this._isLocked;
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;
43
+ if (this._elements.size === 0) {
44
+ return;
45
+ }
36
46
  for (const element of this._elements) {
37
- this._elements.delete(element);
47
+ this._untrack(element);
38
48
  rerender(element.store.latestElement);
39
49
  }
40
50
  },
41
51
  };
42
- export const asyncRerenderLocker = {
52
+ export const renderingRerenderLocker = {
43
53
  _isLocked: false,
44
54
  _elements: new Set(),
45
- get isLocked() {
46
- return this._isLocked;
55
+ _track(element) {
56
+ this._elements.add(element);
57
+ },
58
+ _untrack(element) {
59
+ this._elements.delete(element);
47
60
  },
48
61
  lock() {
49
62
  this._isLocked = true;
50
63
  },
51
- track(element) {
52
- this._elements.add(element);
53
- },
54
64
  flush() {
55
65
  this._isLocked = false;
66
+ if (this._elements.size === 0) {
67
+ return;
68
+ }
56
69
  queueMicrotask(() => {
57
70
  for (const element of this._elements) {
58
- this._elements.delete(element);
71
+ this._untrack(element);
59
72
  rerender(element.store.latestElement);
60
73
  }
61
74
  });
@@ -1,6 +1,6 @@
1
- import type { Many, Maybe } from '../shared';
2
- import type { SimpElement } from './createElement';
3
- import type { HostReference } from './hostAdapter';
1
+ import type { Many, Maybe } from '../shared/index.js';
2
+ import type { SimpElement } from './createElement.js';
3
+ import type { HostReference } from './hostAdapter.js';
4
4
  export declare function unmount(element: Many<SimpElement>): void;
5
5
  export declare function clearElementHostReference(element: Maybe<SimpElement>, parentHostReference: HostReference): void;
6
6
  export declare function remove(element: SimpElement, parentReference: HostReference): void;
@@ -1,6 +1,6 @@
1
- import { hostAdapter } from './hostAdapter';
2
- import { unmountRef } from './ref';
3
- import { lifecycleEventBus } from './lifecycleEventBus';
1
+ import { hostAdapter } from './hostAdapter.js';
2
+ import { unmountRef } from './ref.js';
3
+ import { lifecycleEventBus } from './lifecycleEventBus.js';
4
4
  export function unmount(element) {
5
5
  if (Array.isArray(element)) {
6
6
  for (const child of element) {
@@ -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,4 +1,4 @@
1
- import type { Nullable } from '../shared';
1
+ import type { Nullable } from '../shared/index.js';
2
2
  import type { SimpElement } from '../core/internal.js';
3
3
  export declare function attachElementToDom(element: SimpElement, dom: Node): void;
4
4
  export declare function getElementFromDom(target: Nullable<EventTarget>): Nullable<SimpElement>;
@@ -1,6 +1,6 @@
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
  }
@@ -1,3 +1,3 @@
1
1
  import type { HostAdapter } from '../core/internal.js';
2
- import type { Namespace } from './namespace';
2
+ import type { Namespace } from './namespace.js';
3
3
  export declare const domAdapter: HostAdapter<HTMLElement | SVGElement, Text, Namespace>;
package/dom/domAdapter.js CHANGED
@@ -1,6 +1,6 @@
1
- import { attachElementToDom } from './attach-element-to-dom';
2
- import { mountProps, patchProps, unmountProps } from './props';
3
- import { defaultNamespace } from './namespace';
1
+ import { attachElementToDom } from './attach-element-to-dom.js';
2
+ import { mountProps, patchProps, unmountProps } from './props/index.js';
3
+ import { defaultNamespace } from './namespace.js';
4
4
  export const domAdapter = {
5
5
  createReference(type, namespace) {
6
6
  if (namespace) {
@@ -33,8 +33,13 @@ export const domAdapter = {
33
33
  reference.className = className;
34
34
  }
35
35
  },
36
- setTextContent(reference, text) {
37
- reference.textContent = text;
36
+ setTextContent(reference, text, referenceHasOnlyTextElement) {
37
+ if (referenceHasOnlyTextElement) {
38
+ reference.firstChild.nodeValue = text;
39
+ }
40
+ else {
41
+ reference.textContent = text;
42
+ }
38
43
  },
39
44
  appendChild(parent, child) {
40
45
  parent.appendChild(child);
package/dom/events.d.ts CHANGED
@@ -1,10 +1,13 @@
1
- import type { Nullable } from '../shared';
1
+ import type { Nullable } from '../shared/index.js';
2
2
  type DelegatedEventType = 'click' | 'dblclick' | 'mousedown' | 'mouseup' | 'mousemove' | 'pointerdown' | 'pointerup' | 'pointermove' | 'touchstart' | 'touchmove' | 'touchend' | 'keydown' | 'keyup' | 'focusin' | 'focusout';
3
3
  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;
8
11
  constructor(event: Event);
9
12
  get target(): EventTarget | null;
10
13
  get type(): string;
package/dom/events.js CHANGED
@@ -1,5 +1,5 @@
1
- import { syncRerenderLocker } from '../core/internal.js';
2
- import { getElementFromDom } from './attach-element-to-dom';
1
+ import { batchingRerenderLocker } from '../core/internal.js';
2
+ import { getElementFromDom } from './attach-element-to-dom.js';
3
3
  const eventNameByTypes = {
4
4
  click: 'onClick',
5
5
  dblclick: 'onDblClick',
@@ -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
- syncRerenderLocker.lock();
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
- syncRerenderLocker.flush();
100
+ batchingRerenderLocker.flush();
95
101
  }
96
102
  const captureRegex = /Capture/;
97
103
  export function patchEvent(name, prevValue, nextValue, dom) {
package/dom/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { RefAttributes, SimpElement, SimpNode } from '../core';
2
- import type { Nullable } from '../shared';
1
+ import type { RefAttributes, SimpElement, SimpNode } from '../core/index.js';
2
+ import type { Nullable } from '../shared/index.js';
3
3
 
4
4
  import type { PropertiesHyphen } from 'csstype';
5
5
 
package/dom/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { createRoot, render } from './render';
2
- export { createRoot, render } from './render';
1
+ import { createRoot, render } from './render.js';
2
+ export { createRoot, render } from './render.js';
3
3
  export default { createRoot, render };
@@ -1,5 +1,5 @@
1
1
  import type { SimpElement } from '../../../core/internal.js';
2
- import type { Dict } from '../../../shared';
2
+ import type { Dict } from '../../../shared/index.js';
3
3
  export declare function isEventNameIgnored(element: SimpElement, eventName: string): boolean;
4
4
  export declare function isFormElementControlled(props: Dict): boolean;
5
5
  export declare function addControlledFormElementEventHandlers(element: SimpElement): void;
@@ -1,6 +1,6 @@
1
- import { addControlledInputEventHandlers, isEventNameIgnored as isEventNameIgnoredInput, removeControlledInputEventHandlers, syncControlledInputProps, } from './input';
2
- import { addControlledSelectEventHandlers, isEventNameIgnored as isEventNameIgnoredSelect, removeControlledSelectEventHandlers, syncControlledSelectProps, } from './select';
3
- import { addControlledTextareaEventHandlers, isEventNameIgnored as isEventNameIgnoredTextarea, removeControlledTextareaEventHandlers, syncControlledTextareaProps, } from './textarea';
1
+ import { addControlledInputEventHandlers, isEventNameIgnored as isEventNameIgnoredInput, removeControlledInputEventHandlers, syncControlledInputProps, } from './input.js';
2
+ import { addControlledSelectEventHandlers, isEventNameIgnored as isEventNameIgnoredSelect, removeControlledSelectEventHandlers, syncControlledSelectProps, } from './select.js';
3
+ import { addControlledTextareaEventHandlers, isEventNameIgnored as isEventNameIgnoredTextarea, removeControlledTextareaEventHandlers, syncControlledTextareaProps, } from './textarea.js';
4
4
  export function isEventNameIgnored(element, eventName) {
5
5
  if (element.type === 'input') {
6
6
  return isEventNameIgnoredInput(element.props, eventName);
@@ -1,5 +1,5 @@
1
1
  import type { SimpElement } from '../../../core/internal.js';
2
- import type { Dict } from '../../../shared';
2
+ import type { Dict } from '../../../shared/index.js';
3
3
  export declare function isCheckedType(type: string): boolean;
4
4
  export declare function isEventNameIgnored(props: Dict, eventName: string): boolean;
5
5
  export declare function addControlledInputEventHandlers(dom: HTMLInputElement): void;