rask-ui 0.1.1 → 0.2.1

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 (213) hide show
  1. package/README.md +923 -185
  2. package/dist/component.d.ts +17 -0
  3. package/dist/component.d.ts.map +1 -1
  4. package/dist/createAsync.d.ts +23 -0
  5. package/dist/createAsync.d.ts.map +1 -1
  6. package/dist/createAsync.js +23 -0
  7. package/dist/createContext.d.ts +26 -2
  8. package/dist/createContext.d.ts.map +1 -1
  9. package/dist/createContext.js +31 -5
  10. package/dist/createContext.test.d.ts +2 -0
  11. package/dist/createContext.test.d.ts.map +1 -0
  12. package/dist/createContext.test.js +136 -0
  13. package/dist/createMutation.d.ts +23 -0
  14. package/dist/createMutation.d.ts.map +1 -1
  15. package/dist/createMutation.js +23 -0
  16. package/dist/createQuery.d.ts +23 -0
  17. package/dist/createQuery.d.ts.map +1 -1
  18. package/dist/createQuery.js +23 -0
  19. package/dist/createRef.test.d.ts +2 -0
  20. package/dist/createRef.test.d.ts.map +1 -0
  21. package/dist/createRef.test.js +80 -0
  22. package/dist/createState.d.ts +24 -0
  23. package/dist/createState.d.ts.map +1 -1
  24. package/dist/createState.js +24 -0
  25. package/dist/createView.d.ts +54 -0
  26. package/dist/createView.d.ts.map +1 -0
  27. package/dist/createView.js +68 -0
  28. package/dist/createView.test.d.ts +2 -0
  29. package/dist/createView.test.d.ts.map +1 -0
  30. package/dist/createView.test.js +203 -0
  31. package/dist/error.d.ts.map +1 -1
  32. package/dist/error.js +4 -5
  33. package/dist/error.test.d.ts +2 -0
  34. package/dist/error.test.d.ts.map +1 -0
  35. package/dist/error.test.js +144 -0
  36. package/dist/index.d.ts +3 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +3 -2
  39. package/dist/integration.test.d.ts +2 -0
  40. package/dist/integration.test.d.ts.map +1 -0
  41. package/dist/integration.test.js +155 -0
  42. package/dist/jsx-dev-runtime.d.ts +3 -3
  43. package/dist/jsx-dev-runtime.d.ts.map +1 -1
  44. package/dist/jsx-dev-runtime.js +2 -2
  45. package/dist/jsx-runtime.d.ts +1 -4
  46. package/dist/jsx-runtime.d.ts.map +1 -1
  47. package/dist/jsx-runtime.js +3 -14
  48. package/dist/observation.d.ts +1 -0
  49. package/dist/observation.d.ts.map +1 -1
  50. package/dist/observation.js +5 -0
  51. package/dist/test-setup.d.ts +3 -3
  52. package/dist/test-setup.d.ts.map +1 -1
  53. package/dist/test-setup.js +7 -8
  54. package/dist/tests/class.test.d.ts +2 -0
  55. package/dist/tests/class.test.d.ts.map +1 -0
  56. package/dist/tests/class.test.js +185 -0
  57. package/dist/tests/complex-rendering.test.d.ts +2 -0
  58. package/dist/tests/complex-rendering.test.d.ts.map +1 -0
  59. package/dist/tests/complex-rendering.test.js +400 -0
  60. package/dist/tests/component.cleanup.test.d.ts +2 -0
  61. package/dist/tests/component.cleanup.test.d.ts.map +1 -0
  62. package/dist/tests/component.cleanup.test.js +325 -0
  63. package/dist/tests/component.counter.test.d.ts +2 -0
  64. package/dist/tests/component.counter.test.d.ts.map +1 -0
  65. package/dist/tests/component.counter.test.js +124 -0
  66. package/dist/tests/component.interaction.test.d.ts +2 -0
  67. package/dist/tests/component.interaction.test.d.ts.map +1 -0
  68. package/dist/tests/component.interaction.test.js +73 -0
  69. package/dist/tests/component.props.test.d.ts +2 -0
  70. package/dist/tests/component.props.test.d.ts.map +1 -0
  71. package/dist/tests/component.props.test.js +334 -0
  72. package/dist/tests/component.return-types.test.d.ts +2 -0
  73. package/dist/tests/component.return-types.test.d.ts.map +1 -0
  74. package/dist/tests/component.return-types.test.js +357 -0
  75. package/dist/tests/component.state.test.d.ts +2 -0
  76. package/dist/tests/component.state.test.d.ts.map +1 -0
  77. package/dist/tests/component.state.test.js +135 -0
  78. package/dist/tests/component.test.d.ts +2 -0
  79. package/dist/tests/component.test.d.ts.map +1 -0
  80. package/dist/tests/component.test.js +63 -0
  81. package/dist/tests/createAsync.test.d.ts +2 -0
  82. package/dist/tests/createAsync.test.d.ts.map +1 -0
  83. package/dist/tests/createAsync.test.js +110 -0
  84. package/dist/tests/createContext.test.d.ts +2 -0
  85. package/dist/tests/createContext.test.d.ts.map +1 -0
  86. package/dist/tests/createContext.test.js +141 -0
  87. package/dist/tests/createMutation.test.d.ts +2 -0
  88. package/dist/tests/createMutation.test.d.ts.map +1 -0
  89. package/dist/tests/createMutation.test.js +168 -0
  90. package/dist/tests/createQuery.test.d.ts +2 -0
  91. package/dist/tests/createQuery.test.d.ts.map +1 -0
  92. package/dist/tests/createQuery.test.js +156 -0
  93. package/dist/tests/createRef.test.d.ts +2 -0
  94. package/dist/tests/createRef.test.d.ts.map +1 -0
  95. package/dist/tests/createRef.test.js +84 -0
  96. package/dist/tests/createState.test.d.ts +2 -0
  97. package/dist/tests/createState.test.d.ts.map +1 -0
  98. package/dist/tests/createState.test.js +103 -0
  99. package/dist/tests/createView.test.d.ts +2 -0
  100. package/dist/tests/createView.test.d.ts.map +1 -0
  101. package/dist/tests/createView.test.js +203 -0
  102. package/dist/tests/edge-cases.test.d.ts +2 -0
  103. package/dist/tests/edge-cases.test.d.ts.map +1 -0
  104. package/dist/tests/edge-cases.test.js +637 -0
  105. package/dist/tests/error-no-boundary.test.d.ts +2 -0
  106. package/dist/tests/error-no-boundary.test.d.ts.map +1 -0
  107. package/dist/tests/error-no-boundary.test.js +174 -0
  108. package/dist/tests/error.test.d.ts +2 -0
  109. package/dist/tests/error.test.d.ts.map +1 -0
  110. package/dist/tests/error.test.js +199 -0
  111. package/dist/tests/fragment.test.d.ts +2 -0
  112. package/dist/tests/fragment.test.d.ts.map +1 -0
  113. package/dist/tests/fragment.test.js +618 -0
  114. package/dist/tests/integration.test.d.ts +2 -0
  115. package/dist/tests/integration.test.d.ts.map +1 -0
  116. package/dist/tests/integration.test.js +192 -0
  117. package/dist/tests/keys.test.d.ts +2 -0
  118. package/dist/tests/keys.test.d.ts.map +1 -0
  119. package/dist/tests/keys.test.js +293 -0
  120. package/dist/tests/mount.test.d.ts +2 -0
  121. package/dist/tests/mount.test.d.ts.map +1 -0
  122. package/dist/tests/mount.test.js +91 -0
  123. package/dist/tests/observation.test.d.ts +2 -0
  124. package/dist/tests/observation.test.d.ts.map +1 -0
  125. package/dist/tests/observation.test.js +150 -0
  126. package/dist/tests/patch.test.d.ts +2 -0
  127. package/dist/tests/patch.test.d.ts.map +1 -0
  128. package/dist/tests/patch.test.js +498 -0
  129. package/dist/tests/patchChildren.test.d.ts +2 -0
  130. package/dist/tests/patchChildren.test.d.ts.map +1 -0
  131. package/dist/tests/patchChildren.test.js +387 -0
  132. package/dist/tests/primitives.test.d.ts +2 -0
  133. package/dist/tests/primitives.test.d.ts.map +1 -0
  134. package/dist/tests/primitives.test.js +132 -0
  135. package/dist/vdom/AbstractVNode.d.ts +22 -0
  136. package/dist/vdom/AbstractVNode.d.ts.map +1 -0
  137. package/dist/vdom/AbstractVNode.js +106 -0
  138. package/dist/vdom/ComponentVNode.d.ts +48 -0
  139. package/dist/vdom/ComponentVNode.d.ts.map +1 -0
  140. package/dist/vdom/ComponentVNode.js +209 -0
  141. package/dist/vdom/ElementVNode.d.ts +24 -0
  142. package/dist/vdom/ElementVNode.d.ts.map +1 -0
  143. package/dist/vdom/ElementVNode.js +126 -0
  144. package/dist/vdom/FragmentVNode.d.ts +13 -0
  145. package/dist/vdom/FragmentVNode.d.ts.map +1 -0
  146. package/dist/vdom/FragmentVNode.js +34 -0
  147. package/dist/vdom/RootVNode.d.ts +22 -0
  148. package/dist/vdom/RootVNode.d.ts.map +1 -0
  149. package/dist/vdom/RootVNode.js +55 -0
  150. package/dist/vdom/TextVNode.d.ts +11 -0
  151. package/dist/vdom/TextVNode.d.ts.map +1 -0
  152. package/dist/vdom/TextVNode.js +32 -0
  153. package/dist/vdom/class.test.d.ts +2 -0
  154. package/dist/vdom/class.test.d.ts.map +1 -0
  155. package/dist/vdom/class.test.js +143 -0
  156. package/dist/vdom/complex-rendering.test.d.ts +2 -0
  157. package/dist/vdom/complex-rendering.test.d.ts.map +1 -0
  158. package/dist/vdom/complex-rendering.test.js +400 -0
  159. package/dist/vdom/component.cleanup.test.d.ts +2 -0
  160. package/dist/vdom/component.cleanup.test.d.ts.map +1 -0
  161. package/dist/vdom/component.cleanup.test.js +323 -0
  162. package/dist/vdom/component.counter.test.d.ts +2 -0
  163. package/dist/vdom/component.counter.test.d.ts.map +1 -0
  164. package/dist/vdom/component.counter.test.js +124 -0
  165. package/dist/vdom/component.interaction.test.d.ts +2 -0
  166. package/dist/vdom/component.interaction.test.d.ts.map +1 -0
  167. package/dist/vdom/component.interaction.test.js +73 -0
  168. package/dist/vdom/component.props.test.d.ts +2 -0
  169. package/dist/vdom/component.props.test.d.ts.map +1 -0
  170. package/dist/vdom/component.props.test.js +88 -0
  171. package/dist/vdom/component.return-types.test.d.ts +2 -0
  172. package/dist/vdom/component.return-types.test.d.ts.map +1 -0
  173. package/dist/vdom/component.return-types.test.js +357 -0
  174. package/dist/vdom/component.state.test.d.ts +2 -0
  175. package/dist/vdom/component.state.test.d.ts.map +1 -0
  176. package/dist/vdom/component.state.test.js +129 -0
  177. package/dist/vdom/component.test.d.ts +2 -0
  178. package/dist/vdom/component.test.d.ts.map +1 -0
  179. package/dist/vdom/component.test.js +63 -0
  180. package/dist/vdom/dom-utils.d.ts +9 -0
  181. package/dist/vdom/dom-utils.d.ts.map +1 -0
  182. package/dist/vdom/dom-utils.js +84 -0
  183. package/dist/vdom/edge-cases.test.d.ts +2 -0
  184. package/dist/vdom/edge-cases.test.d.ts.map +1 -0
  185. package/dist/vdom/edge-cases.test.js +637 -0
  186. package/dist/vdom/fragment.test.d.ts +2 -0
  187. package/dist/vdom/fragment.test.d.ts.map +1 -0
  188. package/dist/vdom/fragment.test.js +618 -0
  189. package/dist/vdom/index.d.ts +10 -0
  190. package/dist/vdom/index.d.ts.map +1 -0
  191. package/dist/vdom/index.js +26 -0
  192. package/dist/vdom/keys.test.d.ts +2 -0
  193. package/dist/vdom/keys.test.d.ts.map +1 -0
  194. package/dist/vdom/keys.test.js +293 -0
  195. package/dist/vdom/mount.test.d.ts +2 -0
  196. package/dist/vdom/mount.test.d.ts.map +1 -0
  197. package/dist/vdom/mount.test.js +91 -0
  198. package/dist/vdom/patch.test.d.ts +2 -0
  199. package/dist/vdom/patch.test.d.ts.map +1 -0
  200. package/dist/vdom/patch.test.js +498 -0
  201. package/dist/vdom/patchChildren.test.d.ts +2 -0
  202. package/dist/vdom/patchChildren.test.d.ts.map +1 -0
  203. package/dist/vdom/patchChildren.test.js +392 -0
  204. package/dist/vdom/primitives.test.d.ts +2 -0
  205. package/dist/vdom/primitives.test.d.ts.map +1 -0
  206. package/dist/vdom/primitives.test.js +132 -0
  207. package/dist/vdom/types.d.ts +8 -0
  208. package/dist/vdom/types.d.ts.map +1 -0
  209. package/dist/vdom/types.js +1 -0
  210. package/dist/vdom/utils.d.ts +6 -0
  211. package/dist/vdom/utils.d.ts.map +1 -0
  212. package/dist/vdom/utils.js +63 -0
  213. package/package.json +4 -6
@@ -0,0 +1,54 @@
1
+ type Simplify<T> = {
2
+ [K in keyof T]: T[K];
3
+ } & {};
4
+ type MergeTwo<A extends object, B extends object> = Simplify<Omit<A, keyof B> & B>;
5
+ type MergeMany<T extends readonly object[]> = T extends [
6
+ infer H extends object,
7
+ ...infer R extends object[]
8
+ ] ? MergeTwo<H, MergeMany<R>> : {};
9
+ /**
10
+ * Creates a view that merges multiple objects (reactive or not) into a single object while
11
+ * maintaining reactivity through getters. Properties from later arguments override earlier ones.
12
+ *
13
+ * @warning **Do not destructure the returned view object!** Destructuring breaks reactivity
14
+ * because it extracts plain values instead of maintaining getter access. This is the same rule
15
+ * as other reactive primitives.
16
+ *
17
+ * @example
18
+ * // ❌ Bad - destructuring loses reactivity
19
+ * function Component() {
20
+ * const state = createState({ count: 0 });
21
+ * const helpers = { increment: () => state.count++ };
22
+ * const view = createView(state, helpers);
23
+ * const { count, increment } = view; // Don't do this!
24
+ * return () => <button onClick={increment}>{count}</button>; // Won't update!
25
+ * }
26
+ *
27
+ * // ✅ Good - access properties directly in render
28
+ * function Component() {
29
+ * const state = createState({ count: 0 });
30
+ * const helpers = { increment: () => state.count++ };
31
+ * const view = createView(state, helpers);
32
+ * return () => <button onClick={view.increment}>{view.count}</button>; // Reactive!
33
+ * }
34
+ *
35
+ * @example
36
+ * // Merge multiple reactive objects
37
+ * const state = createState({ count: 0 });
38
+ * const user = createState({ name: "Alice" });
39
+ * const view = createView(state, user);
40
+ * // view has both count and name properties, maintaining reactivity
41
+ *
42
+ * @example
43
+ * // Later arguments override earlier ones
44
+ * const a = { x: 1, y: 2 };
45
+ * const b = { y: 3, z: 4 };
46
+ * const view = createView(a, b);
47
+ * // view.x === 1, view.y === 3, view.z === 4
48
+ *
49
+ * @param args - Objects to merge (reactive or plain objects)
50
+ * @returns A view object with getters for all properties, maintaining reactivity
51
+ */
52
+ export declare function createView<T extends readonly object[]>(...args: T): MergeMany<T>;
53
+ export {};
54
+ //# sourceMappingURL=createView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createView.d.ts","sourceRoot":"","sources":["../src/createView.ts"],"names":[],"mappings":"AAAA,KAAK,QAAQ,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,EAAE,CAAC;AAEjD,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,IAAI,QAAQ,CAC1D,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CACrB,CAAC;AAEF,KAAK,SAAS,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,IAAI,CAAC,SAAS;IACtD,MAAM,CAAC,SAAS,MAAM;IACtB,GAAG,MAAM,CAAC,SAAS,MAAM,EAAE;CAC5B,GACG,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,GACzB,EAAE,CAAC;AAEP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,EACpD,GAAG,IAAI,EAAE,CAAC,GACT,SAAS,CAAC,CAAC,CAAC,CA0Bd"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Creates a view that merges multiple objects (reactive or not) into a single object while
3
+ * maintaining reactivity through getters. Properties from later arguments override earlier ones.
4
+ *
5
+ * @warning **Do not destructure the returned view object!** Destructuring breaks reactivity
6
+ * because it extracts plain values instead of maintaining getter access. This is the same rule
7
+ * as other reactive primitives.
8
+ *
9
+ * @example
10
+ * // ❌ Bad - destructuring loses reactivity
11
+ * function Component() {
12
+ * const state = createState({ count: 0 });
13
+ * const helpers = { increment: () => state.count++ };
14
+ * const view = createView(state, helpers);
15
+ * const { count, increment } = view; // Don't do this!
16
+ * return () => <button onClick={increment}>{count}</button>; // Won't update!
17
+ * }
18
+ *
19
+ * // ✅ Good - access properties directly in render
20
+ * function Component() {
21
+ * const state = createState({ count: 0 });
22
+ * const helpers = { increment: () => state.count++ };
23
+ * const view = createView(state, helpers);
24
+ * return () => <button onClick={view.increment}>{view.count}</button>; // Reactive!
25
+ * }
26
+ *
27
+ * @example
28
+ * // Merge multiple reactive objects
29
+ * const state = createState({ count: 0 });
30
+ * const user = createState({ name: "Alice" });
31
+ * const view = createView(state, user);
32
+ * // view has both count and name properties, maintaining reactivity
33
+ *
34
+ * @example
35
+ * // Later arguments override earlier ones
36
+ * const a = { x: 1, y: 2 };
37
+ * const b = { y: 3, z: 4 };
38
+ * const view = createView(a, b);
39
+ * // view.x === 1, view.y === 3, view.z === 4
40
+ *
41
+ * @param args - Objects to merge (reactive or plain objects)
42
+ * @returns A view object with getters for all properties, maintaining reactivity
43
+ */
44
+ export function createView(...args) {
45
+ const result = {};
46
+ const seen = new Set();
47
+ for (let i = args.length - 1; i >= 0; i--) {
48
+ const src = args[i];
49
+ // mimic Object.assign: only enumerable own property keys
50
+ for (const key of Reflect.ownKeys(src)) {
51
+ if (seen.has(key))
52
+ continue;
53
+ const desc = Object.getOwnPropertyDescriptor(src, key);
54
+ if (!desc || !desc.enumerable)
55
+ continue;
56
+ // Capture the current source for this key (last write wins).
57
+ Object.defineProperty(result, key, {
58
+ enumerable: true,
59
+ configurable: true,
60
+ get: () => src[key],
61
+ // Optional write-through (commented out by default):
62
+ // set: (value) => { (src as any)[key as any] = value; },
63
+ });
64
+ seen.add(key);
65
+ }
66
+ }
67
+ return result;
68
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=createView.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createView.test.d.ts","sourceRoot":"","sources":["../src/createView.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,203 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createView } from './createView';
3
+ import { createState } from './createState';
4
+ import { Observer } from './observation';
5
+ describe('createView', () => {
6
+ it('should merge two plain objects', () => {
7
+ const a = { x: 1, y: 2 };
8
+ const b = { z: 3 };
9
+ const view = createView(a, b);
10
+ expect(view.x).toBe(1);
11
+ expect(view.y).toBe(2);
12
+ expect(view.z).toBe(3);
13
+ });
14
+ it('should allow later arguments to override earlier ones', () => {
15
+ const a = { x: 1, y: 2 };
16
+ const b = { y: 3, z: 4 };
17
+ const view = createView(a, b);
18
+ expect(view.x).toBe(1);
19
+ expect(view.y).toBe(3); // b.y overrides a.y
20
+ expect(view.z).toBe(4);
21
+ });
22
+ it('should maintain reactivity with reactive objects', async () => {
23
+ const state = createState({ count: 0 });
24
+ const view = createView(state);
25
+ let renderCount = 0;
26
+ let lastValue = 0;
27
+ const observer = new Observer(() => {
28
+ renderCount++;
29
+ });
30
+ const dispose = observer.observe();
31
+ lastValue = view.count; // Track the property
32
+ dispose();
33
+ expect(renderCount).toBe(0);
34
+ state.count = 5;
35
+ // Wait for microtask to process notification
36
+ await new Promise((resolve) => {
37
+ queueMicrotask(() => {
38
+ expect(renderCount).toBe(1);
39
+ lastValue = view.count;
40
+ expect(lastValue).toBe(5);
41
+ resolve(undefined);
42
+ });
43
+ });
44
+ });
45
+ it('should merge reactive and plain objects while maintaining reactivity', () => {
46
+ const state = createState({ count: 0 });
47
+ const helpers = {
48
+ increment() {
49
+ state.count++;
50
+ },
51
+ decrement() {
52
+ state.count--;
53
+ },
54
+ };
55
+ const view = createView(state, helpers);
56
+ expect(view.count).toBe(0);
57
+ expect(typeof view.increment).toBe('function');
58
+ expect(typeof view.decrement).toBe('function');
59
+ view.increment();
60
+ expect(view.count).toBe(1);
61
+ view.decrement();
62
+ expect(view.count).toBe(0);
63
+ });
64
+ it('should merge multiple reactive objects', () => {
65
+ const state1 = createState({ count: 0 });
66
+ const state2 = createState({ name: 'Alice' });
67
+ const state3 = createState({ age: 25 });
68
+ const view = createView(state1, state2, state3);
69
+ expect(view.count).toBe(0);
70
+ expect(view.name).toBe('Alice');
71
+ expect(view.age).toBe(25);
72
+ state1.count = 10;
73
+ state2.name = 'Bob';
74
+ state3.age = 30;
75
+ expect(view.count).toBe(10);
76
+ expect(view.name).toBe('Bob');
77
+ expect(view.age).toBe(30);
78
+ });
79
+ it('should reflect changes in source objects', () => {
80
+ const source = { x: 1 };
81
+ const view = createView(source);
82
+ expect(view.x).toBe(1);
83
+ source.x = 2;
84
+ expect(view.x).toBe(2);
85
+ });
86
+ it('should handle property override order correctly', () => {
87
+ const a = { x: 1, y: 2, z: 3 };
88
+ const b = { y: 20 };
89
+ const c = { z: 30 };
90
+ const view = createView(a, b, c);
91
+ expect(view.x).toBe(1); // From a
92
+ expect(view.y).toBe(20); // From b (overrides a)
93
+ expect(view.z).toBe(30); // From c (overrides a)
94
+ });
95
+ it('should only include enumerable properties', () => {
96
+ const obj = { x: 1 };
97
+ Object.defineProperty(obj, 'hidden', {
98
+ value: 42,
99
+ enumerable: false,
100
+ });
101
+ const view = createView(obj);
102
+ expect(view.x).toBe(1);
103
+ expect(view.hidden).toBeUndefined();
104
+ });
105
+ it('should handle symbol keys', () => {
106
+ const sym = Symbol('test');
107
+ const obj = { x: 1, [sym]: 'symbol value' };
108
+ const view = createView(obj);
109
+ expect(view.x).toBe(1);
110
+ expect(view[sym]).toBe('symbol value');
111
+ });
112
+ it('should track dependencies for each property independently', async () => {
113
+ const state = createState({ count: 0, name: 'Alice' });
114
+ const view = createView(state);
115
+ let countRenderCount = 0;
116
+ let nameRenderCount = 0;
117
+ // Observer that only accesses count
118
+ const countObserver = new Observer(() => {
119
+ countRenderCount++;
120
+ });
121
+ const dispose1 = countObserver.observe();
122
+ view.count; // Track count
123
+ dispose1();
124
+ expect(countRenderCount).toBe(0);
125
+ // Change count - should trigger
126
+ state.count = 1;
127
+ await new Promise((resolve) => {
128
+ queueMicrotask(() => {
129
+ expect(countRenderCount).toBe(1);
130
+ resolve(undefined);
131
+ });
132
+ });
133
+ // Change name - should NOT trigger (not tracked)
134
+ state.name = 'Bob';
135
+ await new Promise((resolve) => {
136
+ queueMicrotask(() => {
137
+ expect(countRenderCount).toBe(1); // Still 1
138
+ resolve(undefined);
139
+ });
140
+ });
141
+ // Now track name with a different observer
142
+ const nameObserver = new Observer(() => {
143
+ nameRenderCount++;
144
+ });
145
+ const dispose2 = nameObserver.observe();
146
+ view.name; // Track name
147
+ dispose2();
148
+ expect(nameRenderCount).toBe(0);
149
+ // Change name - should trigger name observer
150
+ state.name = 'Charlie';
151
+ await new Promise((resolve) => {
152
+ queueMicrotask(() => {
153
+ expect(nameRenderCount).toBe(1);
154
+ resolve(undefined);
155
+ });
156
+ });
157
+ });
158
+ it('should return the same value type as source', () => {
159
+ const obj = { nums: [1, 2, 3], nested: { x: 1 } };
160
+ const view = createView(obj);
161
+ expect(Array.isArray(view.nums)).toBe(true);
162
+ expect(view.nums).toEqual([1, 2, 3]);
163
+ expect(typeof view.nested).toBe('object');
164
+ expect(view.nested.x).toBe(1);
165
+ });
166
+ it('should handle empty merge', () => {
167
+ const view = createView({});
168
+ expect(Object.keys(view).length).toBe(0);
169
+ });
170
+ it('should merge single object', () => {
171
+ const obj = { x: 1, y: 2 };
172
+ const view = createView(obj);
173
+ expect(view.x).toBe(1);
174
+ expect(view.y).toBe(2);
175
+ });
176
+ it('should maintain function context', () => {
177
+ const state = createState({ count: 0 });
178
+ const methods = {
179
+ increment() {
180
+ state.count++;
181
+ },
182
+ };
183
+ const view = createView(state, methods);
184
+ expect(view.count).toBe(0);
185
+ view.increment();
186
+ expect(view.count).toBe(1);
187
+ expect(state.count).toBe(1);
188
+ });
189
+ it('should work with reactive state and computed-like patterns', () => {
190
+ const state = createState({ firstName: 'John', lastName: 'Doe' });
191
+ const computed = {
192
+ get fullName() {
193
+ return `${state.firstName} ${state.lastName}`;
194
+ },
195
+ };
196
+ const view = createView(state, computed);
197
+ expect(view.firstName).toBe('John');
198
+ expect(view.lastName).toBe('Doe');
199
+ expect(view.fullName).toBe('John Doe');
200
+ state.firstName = 'Jane';
201
+ expect(view.fullName).toBe('Jane Doe');
202
+ });
203
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAEA,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,GAAG,SAAS,EAAE,CAAC;IACnD,QAAQ,EAAE,GAAG,CAAC;CACf,aAsBA"}
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAEA,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,GAAG,SAAS,EAAE,CAAC;IACnD,QAAQ,EAAE,GAAG,CAAC;CACf,aAqBA"}
package/dist/error.js CHANGED
@@ -1,16 +1,15 @@
1
- import { getCurrentComponent } from "./component";
1
+ import { getCurrentComponent } from "./vdom/ComponentVNode";
2
2
  export function ErrorBoundary(props) {
3
3
  const component = getCurrentComponent();
4
- // Access .error during setup to ensure the error signal is created
5
- // This allows child errors to be caught even during initial render
6
- component.error;
7
4
  return () => {
8
5
  if (component.error) {
9
6
  return props.error(component.error);
10
7
  }
11
8
  // Fix parent relationship: children vnodes were created with wrong parent,
12
9
  // we need to update them to point to this ErrorBoundary component
13
- const children = Array.isArray(props.children) ? props.children : [props.children];
10
+ const children = Array.isArray(props.children)
11
+ ? props.children
12
+ : [props.children];
14
13
  children.forEach((child) => {
15
14
  if (child?.data?.parentComponent) {
16
15
  child.data.parentComponent = component;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=error.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.test.d.ts","sourceRoot":"","sources":["../src/error.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,144 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "./jsx-runtime";
2
+ import { describe, it, expect } from "vitest";
3
+ import { ErrorBoundary } from "./error";
4
+ import { renderComponent } from "./test-setup";
5
+ describe("ErrorBoundary", () => {
6
+ it("should render children when no error occurs", async () => {
7
+ function SafeChild() {
8
+ return () => _jsx("div", { children: "Safe content" });
9
+ }
10
+ function TestComponent() {
11
+ return () => (_jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Error: ", String(error)] }), children: _jsx(SafeChild, {}) }));
12
+ }
13
+ const { container, unmount } = renderComponent(_jsx(TestComponent, {}));
14
+ await new Promise((resolve) => setTimeout(resolve, 10));
15
+ expect(container.textContent).toContain("Safe content");
16
+ expect(container.textContent).not.toContain("Error:");
17
+ unmount();
18
+ });
19
+ it("should catch errors thrown in child components", async () => {
20
+ function ThrowingChild() {
21
+ return () => {
22
+ throw new Error("Child component error");
23
+ };
24
+ }
25
+ function TestComponent() {
26
+ return () => (_jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Error: ", String(error)] }), children: _jsx(ThrowingChild, {}) }));
27
+ }
28
+ const { container, unmount } = renderComponent(_jsx(TestComponent, {}));
29
+ await new Promise((resolve) => setTimeout(resolve, 10));
30
+ expect(container.textContent).toContain("Error:");
31
+ expect(container.textContent).toContain("Child component error");
32
+ unmount();
33
+ });
34
+ it("should render custom error UI", async () => {
35
+ function ThrowingChild() {
36
+ return () => {
37
+ throw new Error("Something went wrong");
38
+ };
39
+ }
40
+ function TestComponent() {
41
+ return () => (_jsx(ErrorBoundary, { error: (error) => (_jsxs("div", { class: "error-ui", children: [_jsx("h1", { children: "Oops!" }), _jsx("p", { children: String(error) })] })), children: _jsx(ThrowingChild, {}) }));
42
+ }
43
+ const { container, unmount } = renderComponent(_jsx(TestComponent, {}));
44
+ await new Promise((resolve) => setTimeout(resolve, 10));
45
+ const errorUI = document.querySelector(".error-ui");
46
+ expect(errorUI).not.toBeNull();
47
+ expect(errorUI?.querySelector("h1")?.textContent).toBe("Oops!");
48
+ expect(errorUI?.textContent).toContain("Something went wrong");
49
+ unmount();
50
+ });
51
+ it("should handle multiple children", async () => {
52
+ function SafeChild1() {
53
+ return () => _jsx("div", { children: "Child 1" });
54
+ }
55
+ function SafeChild2() {
56
+ return () => _jsx("div", { children: "Child 2" });
57
+ }
58
+ function TestComponent() {
59
+ return () => (_jsxs(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Error: ", String(error)] }), children: [_jsx(SafeChild1, {}), _jsx(SafeChild2, {})] }));
60
+ }
61
+ const { container, unmount } = renderComponent(_jsx(TestComponent, {}));
62
+ await new Promise((resolve) => setTimeout(resolve, 10));
63
+ expect(container.textContent).toContain("Child 1");
64
+ expect(container.textContent).toContain("Child 2");
65
+ unmount();
66
+ });
67
+ it("should catch errors from nested children", async () => {
68
+ function DeepChild() {
69
+ return () => {
70
+ throw new Error("Deep error");
71
+ };
72
+ }
73
+ function MiddleChild() {
74
+ return () => _jsx(DeepChild, {});
75
+ }
76
+ function TestComponent() {
77
+ return () => (_jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Caught: ", String(error)] }), children: _jsx(MiddleChild, {}) }));
78
+ }
79
+ const { container, unmount } = renderComponent(_jsx(TestComponent, {}));
80
+ await new Promise((resolve) => setTimeout(resolve, 10));
81
+ expect(container.textContent).toContain("Caught:");
82
+ expect(container.textContent).toContain("Deep error");
83
+ unmount();
84
+ });
85
+ it("should allow nested error boundaries", async () => {
86
+ function ThrowingChild() {
87
+ return () => {
88
+ throw new Error("Inner error");
89
+ };
90
+ }
91
+ function TestComponent() {
92
+ return () => (_jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Outer: ", String(error)] }), children: _jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Inner: ", String(error)] }), children: _jsx(ThrowingChild, {}) }) }));
93
+ }
94
+ const { container, unmount } = renderComponent(_jsx(TestComponent, {}));
95
+ await new Promise((resolve) => setTimeout(resolve, 10));
96
+ // Inner boundary should catch the error
97
+ expect(container.textContent).toContain("Inner:");
98
+ expect(container.textContent).not.toContain("Outer:");
99
+ unmount();
100
+ });
101
+ it("should handle string errors", async () => {
102
+ function ThrowingChild() {
103
+ return () => {
104
+ throw "String error";
105
+ };
106
+ }
107
+ function TestComponent() {
108
+ return () => (_jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Error: ", String(error)] }), children: _jsx(ThrowingChild, {}) }));
109
+ }
110
+ const { container, unmount } = renderComponent(_jsx(TestComponent, {}));
111
+ await new Promise((resolve) => setTimeout(resolve, 10));
112
+ expect(container.textContent).toContain("String error");
113
+ unmount();
114
+ });
115
+ it("should handle object errors", async () => {
116
+ function ThrowingChild() {
117
+ return () => {
118
+ throw { message: "Custom error object", code: 500 };
119
+ };
120
+ }
121
+ function TestComponent() {
122
+ return () => (_jsx(ErrorBoundary, { error: (error) => (_jsxs("div", { children: ["Error: ", error.message, " (Code: ", error.code, ")"] })), children: _jsx(ThrowingChild, {}) }));
123
+ }
124
+ const { container, unmount } = renderComponent(_jsx(TestComponent, {}));
125
+ await new Promise((resolve) => setTimeout(resolve, 10));
126
+ expect(container.textContent).toContain("Custom error object");
127
+ expect(container.textContent).toContain("500");
128
+ unmount();
129
+ });
130
+ it("should switch back to children if error is cleared", async () => {
131
+ // Note: This test demonstrates the current behavior
132
+ // In practice, error clearing would require additional implementation
133
+ function SafeChild() {
134
+ return () => _jsx("div", { children: "Safe content" });
135
+ }
136
+ function TestComponent() {
137
+ return () => (_jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Error: ", String(error)] }), children: _jsx(SafeChild, {}) }));
138
+ }
139
+ const { container, unmount } = renderComponent(_jsx(TestComponent, {}));
140
+ await new Promise((resolve) => setTimeout(resolve, 10));
141
+ expect(container.textContent).toContain("Safe content");
142
+ unmount();
143
+ });
144
+ });
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { render } from "./render";
2
- export { onMount, onCleanup } from "./component";
1
+ export { render } from "./vdom";
2
+ export { onCleanup, onMount } from "./vdom/ComponentVNode";
3
3
  export { createContext } from "./createContext";
4
4
  export { createState } from "./createState";
5
5
  export { createAsync } from "./createAsync";
@@ -7,4 +7,5 @@ export { ErrorBoundary } from "./error";
7
7
  export { createQuery } from "./createQuery";
8
8
  export { createMutation } from "./createMutation";
9
9
  export { createRef } from "./createRef";
10
+ export { createView } from "./createView";
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { render } from "./render";
2
- export { onMount, onCleanup } from "./component";
1
+ export { render } from "./vdom";
2
+ export { onCleanup, onMount } from "./vdom/ComponentVNode";
3
3
  export { createContext } from "./createContext";
4
4
  export { createState } from "./createState";
5
5
  export { createAsync } from "./createAsync";
@@ -7,3 +7,4 @@ export { ErrorBoundary } from "./error";
7
7
  export { createQuery } from "./createQuery";
8
8
  export { createMutation } from "./createMutation";
9
9
  export { createRef } from "./createRef";
10
+ export { createView } from "./createView";
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.test.d.ts","sourceRoot":"","sources":["../src/integration.test.tsx"],"names":[],"mappings":""}