rask-ui 0.21.0 → 0.22.0

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 (109) hide show
  1. package/dist/asyncState.d.ts +16 -0
  2. package/dist/asyncState.d.ts.map +1 -0
  3. package/dist/asyncState.js +24 -0
  4. package/dist/component.d.ts.map +1 -1
  5. package/dist/component.js +4 -1
  6. package/dist/context.d.ts +5 -0
  7. package/dist/context.d.ts.map +1 -0
  8. package/dist/context.js +29 -0
  9. package/dist/createAsync.test.d.ts +2 -0
  10. package/dist/createAsync.test.d.ts.map +1 -0
  11. package/dist/createAsync.test.js +110 -0
  12. package/dist/createContext.d.ts +20 -21
  13. package/dist/createContext.d.ts.map +1 -1
  14. package/dist/createContext.js +29 -25
  15. package/dist/createMutation.test.d.ts +2 -0
  16. package/dist/createMutation.test.d.ts.map +1 -0
  17. package/dist/createMutation.test.js +168 -0
  18. package/dist/createQuery.test.d.ts +2 -0
  19. package/dist/createQuery.test.d.ts.map +1 -0
  20. package/dist/createQuery.test.js +156 -0
  21. package/dist/createRef.d.ts +6 -0
  22. package/dist/createRef.d.ts.map +1 -0
  23. package/dist/createRef.js +8 -0
  24. package/dist/createState.d.ts +0 -2
  25. package/dist/createState.d.ts.map +1 -1
  26. package/dist/createState.js +5 -40
  27. package/dist/createState.test.d.ts.map +1 -0
  28. package/dist/createState.test.js +111 -0
  29. package/dist/createView.d.ts +44 -18
  30. package/dist/createView.d.ts.map +1 -1
  31. package/dist/createView.js +48 -57
  32. package/dist/createView.test.d.ts.map +1 -0
  33. package/dist/{tests/createView.test.js → createView.test.js} +40 -40
  34. package/dist/error.d.ts +14 -3
  35. package/dist/error.d.ts.map +1 -1
  36. package/dist/error.js +15 -14
  37. package/dist/jsx.d.ts +256 -10
  38. package/dist/observation.test.d.ts.map +1 -0
  39. package/dist/observation.test.js +150 -0
  40. package/dist/suspense.d.ts +25 -0
  41. package/dist/suspense.d.ts.map +1 -0
  42. package/dist/suspense.js +97 -0
  43. package/dist/test-setup.d.ts +16 -0
  44. package/dist/test-setup.d.ts.map +1 -0
  45. package/dist/test-setup.js +40 -0
  46. package/dist/test.d.ts +2 -0
  47. package/dist/test.d.ts.map +1 -0
  48. package/dist/test.js +24 -0
  49. package/dist/useCatchError.d.ts +3 -1
  50. package/dist/useCatchError.d.ts.map +1 -1
  51. package/dist/useCatchError.js +4 -3
  52. package/package.json +2 -2
  53. package/swc-plugin/target/wasm32-wasip1/release/swc_plugin_rask_component.wasm +0 -0
  54. package/dist/createComputed.d.ts +0 -4
  55. package/dist/createComputed.d.ts.map +0 -1
  56. package/dist/createComputed.js +0 -69
  57. package/dist/createEffect.d.ts +0 -2
  58. package/dist/createEffect.d.ts.map +0 -1
  59. package/dist/createEffect.js +0 -29
  60. package/dist/createRouter.d.ts +0 -8
  61. package/dist/createRouter.d.ts.map +0 -1
  62. package/dist/createRouter.js +0 -27
  63. package/dist/createTask.d.ts +0 -31
  64. package/dist/createTask.d.ts.map +0 -1
  65. package/dist/createTask.js +0 -79
  66. package/dist/patchInferno.d.ts +0 -6
  67. package/dist/patchInferno.d.ts.map +0 -1
  68. package/dist/patchInferno.js +0 -53
  69. package/dist/scheduler.d.ts +0 -4
  70. package/dist/scheduler.d.ts.map +0 -1
  71. package/dist/scheduler.js +0 -107
  72. package/dist/tests/batch.test.d.ts +0 -2
  73. package/dist/tests/batch.test.d.ts.map +0 -1
  74. package/dist/tests/batch.test.js +0 -244
  75. package/dist/tests/createComputed.test.d.ts +0 -2
  76. package/dist/tests/createComputed.test.d.ts.map +0 -1
  77. package/dist/tests/createComputed.test.js +0 -257
  78. package/dist/tests/createContext.test.d.ts +0 -2
  79. package/dist/tests/createContext.test.d.ts.map +0 -1
  80. package/dist/tests/createContext.test.js +0 -136
  81. package/dist/tests/createEffect.test.d.ts +0 -2
  82. package/dist/tests/createEffect.test.d.ts.map +0 -1
  83. package/dist/tests/createEffect.test.js +0 -467
  84. package/dist/tests/createState.test.d.ts.map +0 -1
  85. package/dist/tests/createState.test.js +0 -144
  86. package/dist/tests/createTask.test.d.ts +0 -2
  87. package/dist/tests/createTask.test.d.ts.map +0 -1
  88. package/dist/tests/createTask.test.js +0 -322
  89. package/dist/tests/createView.test.d.ts.map +0 -1
  90. package/dist/tests/error.test.d.ts +0 -2
  91. package/dist/tests/error.test.d.ts.map +0 -1
  92. package/dist/tests/error.test.js +0 -168
  93. package/dist/tests/observation.test.d.ts.map +0 -1
  94. package/dist/tests/observation.test.js +0 -341
  95. package/dist/useComputed.d.ts +0 -5
  96. package/dist/useComputed.d.ts.map +0 -1
  97. package/dist/useComputed.js +0 -69
  98. package/dist/useQuery.d.ts +0 -25
  99. package/dist/useQuery.d.ts.map +0 -1
  100. package/dist/useQuery.js +0 -25
  101. package/dist/useSuspendAsync.d.ts +0 -18
  102. package/dist/useSuspendAsync.d.ts.map +0 -1
  103. package/dist/useSuspendAsync.js +0 -37
  104. package/dist/useTask.d.ts +0 -25
  105. package/dist/useTask.d.ts.map +0 -1
  106. package/dist/useTask.js +0 -70
  107. /package/dist/{tests/createState.test.d.ts → createState.test.d.ts} +0 -0
  108. /package/dist/{tests/createView.test.d.ts → createView.test.d.ts} +0 -0
  109. /package/dist/{tests/observation.test.d.ts → observation.test.d.ts} +0 -0
@@ -1,9 +1,9 @@
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", () => {
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
7
  const a = { x: 1, y: 2 };
8
8
  const b = { z: 3 };
9
9
  const view = createView(a, b);
@@ -11,7 +11,7 @@ describe("createView", () => {
11
11
  expect(view.y).toBe(2);
12
12
  expect(view.z).toBe(3);
13
13
  });
14
- it("should allow later arguments to override earlier ones", () => {
14
+ it('should allow later arguments to override earlier ones', () => {
15
15
  const a = { x: 1, y: 2 };
16
16
  const b = { y: 3, z: 4 };
17
17
  const view = createView(a, b);
@@ -19,7 +19,7 @@ describe("createView", () => {
19
19
  expect(view.y).toBe(3); // b.y overrides a.y
20
20
  expect(view.z).toBe(4);
21
21
  });
22
- it("should maintain reactivity with reactive objects", async () => {
22
+ it('should maintain reactivity with reactive objects', async () => {
23
23
  const state = createState({ count: 0 });
24
24
  const view = createView(state);
25
25
  let renderCount = 0;
@@ -42,7 +42,7 @@ describe("createView", () => {
42
42
  });
43
43
  });
44
44
  });
45
- it("should merge reactive and plain objects while maintaining reactivity", () => {
45
+ it('should merge reactive and plain objects while maintaining reactivity', () => {
46
46
  const state = createState({ count: 0 });
47
47
  const helpers = {
48
48
  increment() {
@@ -54,36 +54,36 @@ describe("createView", () => {
54
54
  };
55
55
  const view = createView(state, helpers);
56
56
  expect(view.count).toBe(0);
57
- expect(typeof view.increment).toBe("function");
58
- expect(typeof view.decrement).toBe("function");
57
+ expect(typeof view.increment).toBe('function');
58
+ expect(typeof view.decrement).toBe('function');
59
59
  view.increment();
60
60
  expect(view.count).toBe(1);
61
61
  view.decrement();
62
62
  expect(view.count).toBe(0);
63
63
  });
64
- it("should merge multiple reactive objects", () => {
64
+ it('should merge multiple reactive objects', () => {
65
65
  const state1 = createState({ count: 0 });
66
- const state2 = createState({ name: "Alice" });
66
+ const state2 = createState({ name: 'Alice' });
67
67
  const state3 = createState({ age: 25 });
68
68
  const view = createView(state1, state2, state3);
69
69
  expect(view.count).toBe(0);
70
- expect(view.name).toBe("Alice");
70
+ expect(view.name).toBe('Alice');
71
71
  expect(view.age).toBe(25);
72
72
  state1.count = 10;
73
- state2.name = "Bob";
73
+ state2.name = 'Bob';
74
74
  state3.age = 30;
75
75
  expect(view.count).toBe(10);
76
- expect(view.name).toBe("Bob");
76
+ expect(view.name).toBe('Bob');
77
77
  expect(view.age).toBe(30);
78
78
  });
79
- it("should reflect changes in source objects", () => {
79
+ it('should reflect changes in source objects', () => {
80
80
  const source = { x: 1 };
81
81
  const view = createView(source);
82
82
  expect(view.x).toBe(1);
83
83
  source.x = 2;
84
84
  expect(view.x).toBe(2);
85
85
  });
86
- it("should handle property override order correctly", () => {
86
+ it('should handle property override order correctly', () => {
87
87
  const a = { x: 1, y: 2, z: 3 };
88
88
  const b = { y: 20 };
89
89
  const c = { z: 30 };
@@ -92,9 +92,9 @@ describe("createView", () => {
92
92
  expect(view.y).toBe(20); // From b (overrides a)
93
93
  expect(view.z).toBe(30); // From c (overrides a)
94
94
  });
95
- it("should only include enumerable properties", () => {
95
+ it('should only include enumerable properties', () => {
96
96
  const obj = { x: 1 };
97
- Object.defineProperty(obj, "hidden", {
97
+ Object.defineProperty(obj, 'hidden', {
98
98
  value: 42,
99
99
  enumerable: false,
100
100
  });
@@ -102,15 +102,15 @@ describe("createView", () => {
102
102
  expect(view.x).toBe(1);
103
103
  expect(view.hidden).toBeUndefined();
104
104
  });
105
- it("should handle symbol keys", () => {
106
- const sym = Symbol("test");
107
- const obj = { x: 1, [sym]: "symbol value" };
105
+ it('should handle symbol keys', () => {
106
+ const sym = Symbol('test');
107
+ const obj = { x: 1, [sym]: 'symbol value' };
108
108
  const view = createView(obj);
109
109
  expect(view.x).toBe(1);
110
- expect(view[sym]).toBe("symbol value");
110
+ expect(view[sym]).toBe('symbol value');
111
111
  });
112
- it("should track dependencies for each property independently", async () => {
113
- const state = createState({ count: 0, name: "Alice" });
112
+ it('should track dependencies for each property independently', async () => {
113
+ const state = createState({ count: 0, name: 'Alice' });
114
114
  const view = createView(state);
115
115
  let countRenderCount = 0;
116
116
  let nameRenderCount = 0;
@@ -131,7 +131,7 @@ describe("createView", () => {
131
131
  });
132
132
  });
133
133
  // Change name - should NOT trigger (not tracked)
134
- state.name = "Bob";
134
+ state.name = 'Bob';
135
135
  await new Promise((resolve) => {
136
136
  queueMicrotask(() => {
137
137
  expect(countRenderCount).toBe(1); // Still 1
@@ -147,7 +147,7 @@ describe("createView", () => {
147
147
  dispose2();
148
148
  expect(nameRenderCount).toBe(0);
149
149
  // Change name - should trigger name observer
150
- state.name = "Charlie";
150
+ state.name = 'Charlie';
151
151
  await new Promise((resolve) => {
152
152
  queueMicrotask(() => {
153
153
  expect(nameRenderCount).toBe(1);
@@ -155,25 +155,25 @@ describe("createView", () => {
155
155
  });
156
156
  });
157
157
  });
158
- it("should return the same value type as source", () => {
158
+ it('should return the same value type as source', () => {
159
159
  const obj = { nums: [1, 2, 3], nested: { x: 1 } };
160
160
  const view = createView(obj);
161
161
  expect(Array.isArray(view.nums)).toBe(true);
162
162
  expect(view.nums).toEqual([1, 2, 3]);
163
- expect(typeof view.nested).toBe("object");
163
+ expect(typeof view.nested).toBe('object');
164
164
  expect(view.nested.x).toBe(1);
165
165
  });
166
- it("should handle empty merge", () => {
166
+ it('should handle empty merge', () => {
167
167
  const view = createView({});
168
168
  expect(Object.keys(view).length).toBe(0);
169
169
  });
170
- it("should merge single object", () => {
170
+ it('should merge single object', () => {
171
171
  const obj = { x: 1, y: 2 };
172
172
  const view = createView(obj);
173
173
  expect(view.x).toBe(1);
174
174
  expect(view.y).toBe(2);
175
175
  });
176
- it("should maintain function context", () => {
176
+ it('should maintain function context', () => {
177
177
  const state = createState({ count: 0 });
178
178
  const methods = {
179
179
  increment() {
@@ -186,18 +186,18 @@ describe("createView", () => {
186
186
  expect(view.count).toBe(1);
187
187
  expect(state.count).toBe(1);
188
188
  });
189
- it("should work with reactive state and computed-like patterns", () => {
190
- const state = createState({ firstName: "John", lastName: "Doe" });
189
+ it('should work with reactive state and computed-like patterns', () => {
190
+ const state = createState({ firstName: 'John', lastName: 'Doe' });
191
191
  const computed = {
192
192
  get fullName() {
193
193
  return `${state.firstName} ${state.lastName}`;
194
194
  },
195
195
  };
196
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");
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
202
  });
203
203
  });
package/dist/error.d.ts CHANGED
@@ -1,5 +1,16 @@
1
- export declare const CatchErrorContext: import("./createContext").Context<(error: unknown) => void>;
2
- export declare function useCatchError(): {
1
+ import { Component, VNode } from "inferno";
2
+ export declare class ErrorBoundary extends Component<{
3
+ children: any;
4
+ error: (error: unknown) => VNode;
5
+ }, {
3
6
  error: unknown;
4
- };
7
+ }> {
8
+ getChildContext(): {
9
+ notifyError: (error: unknown) => void;
10
+ };
11
+ state: {
12
+ error: null;
13
+ };
14
+ render(): any;
15
+ }
5
16
  //# sourceMappingURL=error.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,iBAAiB,4CAAyB,OAAO,KAAK,IAAI,CAAG,CAAC;AAE3E,wBAAgB,aAAa;WAQK,OAAO;EAOxC"}
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAE3C,qBAAa,aAAc,SAAQ,SAAS,CAC1C;IAAE,QAAQ,EAAE,GAAG,CAAC;IAAC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAA;CAAE,EACnD;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CACnB;IACC,eAAe;6BAEU,OAAO;;IAKhC,KAAK;;MAAmB;IAExB,MAAM;CAOP"}
package/dist/error.js CHANGED
@@ -1,16 +1,17 @@
1
- import { useState } from "./useState";
2
- import { createContext, useInjectContext } from "./createContext";
3
- import { getCurrentComponent } from "./component";
4
- export const CatchErrorContext = createContext();
5
- export function useCatchError() {
6
- const currentComponent = getCurrentComponent();
7
- if (!currentComponent || currentComponent.isRendering) {
8
- throw new Error("Only use the useCatchError hook in setup");
1
+ import { Component } from "inferno";
2
+ export class ErrorBoundary extends Component {
3
+ getChildContext() {
4
+ return {
5
+ notifyError: (error) => {
6
+ this.setState({ error });
7
+ },
8
+ };
9
+ }
10
+ state = { error: null };
11
+ render() {
12
+ if (this.state.error) {
13
+ return this.props.error(this.state.error);
14
+ }
15
+ return this.props.children;
9
16
  }
10
- const inject = useInjectContext(CatchErrorContext);
11
- const state = useState({
12
- error: null,
13
- });
14
- inject((error) => (state.error = error));
15
- return state;
16
17
  }
package/dist/jsx.d.ts CHANGED
@@ -1,11 +1,257 @@
1
1
  // JSX type definitions
2
- // Re-export Inferno's comprehensive JSX types
3
- import 'inferno';
4
-
5
- // Re-export useful Inferno types for convenience
6
- export type {
7
- InfernoNode,
8
- InfernoChild,
9
- Component,
10
- ComponentType,
11
- } from 'inferno';
2
+ // Note: This is JSXInternal, which gets renamed to JSX on export from jsx-runtime
3
+ import type { Ref } from "./state";
4
+
5
+ export namespace JSXInternal {
6
+ export type Element = any;
7
+
8
+ export interface ElementAttributesProperty {
9
+ props: {};
10
+ }
11
+
12
+ export interface ElementChildrenAttribute {
13
+ children: {};
14
+ }
15
+
16
+ export interface IntrinsicAttributes {
17
+ key?: any;
18
+ }
19
+
20
+ // CSS Properties
21
+ export type CSSProperties = {
22
+ [key: string]: string | number | undefined;
23
+ };
24
+
25
+ // Common HTML Attributes
26
+ export interface HTMLAttributes<T = HTMLElement> {
27
+ ref?: Ref<T> | ((element: T | null) => void);
28
+ id?: string;
29
+ class?: string | Record<string, boolean>;
30
+ style?: string | CSSProperties;
31
+ title?: string;
32
+ role?: string;
33
+ tabIndex?: number;
34
+
35
+ onClick?: (event: MouseEvent) => void;
36
+ onDblClick?: (event: MouseEvent) => void;
37
+ onChange?: (event: Event) => void;
38
+ onInput?: (event: Event) => void;
39
+ onSubmit?: (event: Event) => void;
40
+ onFocus?: (event: FocusEvent) => void;
41
+ onBlur?: (event: FocusEvent) => void;
42
+ onKeyDown?: (event: KeyboardEvent) => void;
43
+ onKeyUp?: (event: KeyboardEvent) => void;
44
+ onKeyPress?: (event: KeyboardEvent) => void;
45
+ onMouseDown?: (event: MouseEvent) => void;
46
+ onMouseUp?: (event: MouseEvent) => void;
47
+ onMouseEnter?: (event: MouseEvent) => void;
48
+ onMouseLeave?: (event: MouseEvent) => void;
49
+ onMouseMove?: (event: MouseEvent) => void;
50
+ onMouseOver?: (event: MouseEvent) => void;
51
+ onMouseOut?: (event: MouseEvent) => void;
52
+ onWheel?: (event: WheelEvent) => void;
53
+ onScroll?: (event: Event) => void;
54
+ onTouchStart?: (event: TouchEvent) => void;
55
+ onTouchEnd?: (event: TouchEvent) => void;
56
+ onTouchMove?: (event: TouchEvent) => void;
57
+ onTouchCancel?: (event: TouchEvent) => void;
58
+
59
+ [key: `aria-${string}`]: string | boolean | number | undefined;
60
+ [key: `data-${string}`]: string | boolean | number | undefined;
61
+
62
+ children?: any;
63
+ }
64
+
65
+ export interface AnchorHTMLAttributes<T = HTMLAnchorElement>
66
+ extends HTMLAttributes<T> {
67
+ href?: string;
68
+ target?: "_blank" | "_self" | "_parent" | "_top";
69
+ rel?: string;
70
+ download?: string;
71
+ }
72
+
73
+ export interface ButtonHTMLAttributes<T = HTMLButtonElement>
74
+ extends HTMLAttributes<T> {
75
+ type?: "button" | "submit" | "reset";
76
+ disabled?: boolean;
77
+ name?: string;
78
+ value?: string;
79
+ }
80
+
81
+ export interface FormHTMLAttributes<T = HTMLFormElement>
82
+ extends HTMLAttributes<T> {
83
+ action?: string;
84
+ method?: "get" | "post";
85
+ enctype?: string;
86
+ target?: string;
87
+ noValidate?: boolean;
88
+ }
89
+
90
+ export interface InputHTMLAttributes<T = HTMLInputElement>
91
+ extends HTMLAttributes<T> {
92
+ type?:
93
+ | "button"
94
+ | "checkbox"
95
+ | "color"
96
+ | "date"
97
+ | "datetime-local"
98
+ | "email"
99
+ | "file"
100
+ | "hidden"
101
+ | "image"
102
+ | "month"
103
+ | "number"
104
+ | "password"
105
+ | "radio"
106
+ | "range"
107
+ | "reset"
108
+ | "search"
109
+ | "submit"
110
+ | "tel"
111
+ | "text"
112
+ | "time"
113
+ | "url"
114
+ | "week";
115
+ value?: string | number;
116
+ defaultValue?: string | number;
117
+ placeholder?: string;
118
+ disabled?: boolean;
119
+ required?: boolean;
120
+ readOnly?: boolean;
121
+ name?: string;
122
+ checked?: boolean;
123
+ defaultChecked?: boolean;
124
+ min?: string | number;
125
+ max?: string | number;
126
+ step?: string | number;
127
+ pattern?: string;
128
+ accept?: string;
129
+ multiple?: boolean;
130
+ autoComplete?: string;
131
+ autoFocus?: boolean;
132
+ }
133
+
134
+ export interface LabelHTMLAttributes<T = HTMLLabelElement>
135
+ extends HTMLAttributes<T> {
136
+ htmlFor?: string;
137
+ for?: string;
138
+ }
139
+
140
+ export interface SelectHTMLAttributes<T = HTMLSelectElement>
141
+ extends HTMLAttributes<T> {
142
+ value?: string | string[];
143
+ defaultValue?: string | string[];
144
+ disabled?: boolean;
145
+ required?: boolean;
146
+ name?: string;
147
+ multiple?: boolean;
148
+ size?: number;
149
+ }
150
+
151
+ export interface OptionHTMLAttributes<T = HTMLOptionElement>
152
+ extends HTMLAttributes<T> {
153
+ value?: string | number;
154
+ selected?: boolean;
155
+ disabled?: boolean;
156
+ label?: string;
157
+ }
158
+
159
+ export interface TextareaHTMLAttributes<T = HTMLTextAreaElement>
160
+ extends HTMLAttributes<T> {
161
+ value?: string;
162
+ defaultValue?: string;
163
+ placeholder?: string;
164
+ disabled?: boolean;
165
+ required?: boolean;
166
+ readOnly?: boolean;
167
+ name?: string;
168
+ rows?: number;
169
+ cols?: number;
170
+ maxLength?: number;
171
+ wrap?: "soft" | "hard";
172
+ }
173
+
174
+ export interface FieldsetHTMLAttributes<T = HTMLFieldSetElement>
175
+ extends HTMLAttributes<T> {
176
+ disabled?: boolean;
177
+ name?: string;
178
+ }
179
+
180
+ export interface ImgHTMLAttributes<T = HTMLImageElement>
181
+ extends HTMLAttributes<T> {
182
+ src?: string;
183
+ alt?: string;
184
+ width?: number | string;
185
+ height?: number | string;
186
+ loading?: "eager" | "lazy";
187
+ crossOrigin?: "anonymous" | "use-credentials";
188
+ }
189
+
190
+ export interface SVGAttributes<T = SVGElement> extends HTMLAttributes<T> {
191
+ xmlns?: string;
192
+ viewBox?: string;
193
+ width?: number | string;
194
+ height?: number | string;
195
+ fill?: string;
196
+ stroke?: string;
197
+ strokeWidth?: number | string;
198
+ }
199
+
200
+ // Intrinsic Elements
201
+ export interface IntrinsicElements {
202
+ a: AnchorHTMLAttributes<HTMLAnchorElement>;
203
+ abbr: HTMLAttributes<HTMLElement>;
204
+ address: HTMLAttributes<HTMLElement>;
205
+ article: HTMLAttributes<HTMLElement>;
206
+ aside: HTMLAttributes<HTMLElement>;
207
+ b: HTMLAttributes<HTMLElement>;
208
+ blockquote: HTMLAttributes<HTMLQuoteElement>;
209
+ body: HTMLAttributes<HTMLBodyElement>;
210
+ br: HTMLAttributes<HTMLBRElement>;
211
+ button: ButtonHTMLAttributes<HTMLButtonElement>;
212
+ canvas: HTMLAttributes<HTMLCanvasElement>;
213
+ code: HTMLAttributes<HTMLElement>;
214
+ div: HTMLAttributes<HTMLDivElement>;
215
+ em: HTMLAttributes<HTMLElement>;
216
+ fieldset: FieldsetHTMLAttributes<HTMLFieldSetElement>;
217
+ footer: HTMLAttributes<HTMLElement>;
218
+ form: FormHTMLAttributes<HTMLFormElement>;
219
+ h1: HTMLAttributes<HTMLHeadingElement>;
220
+ h2: HTMLAttributes<HTMLHeadingElement>;
221
+ h3: HTMLAttributes<HTMLHeadingElement>;
222
+ h4: HTMLAttributes<HTMLHeadingElement>;
223
+ h5: HTMLAttributes<HTMLHeadingElement>;
224
+ h6: HTMLAttributes<HTMLHeadingElement>;
225
+ head: HTMLAttributes<HTMLHeadElement>;
226
+ header: HTMLAttributes<HTMLElement>;
227
+ hr: HTMLAttributes<HTMLHRElement>;
228
+ html: HTMLAttributes<HTMLHtmlElement>;
229
+ i: HTMLAttributes<HTMLElement>;
230
+ img: ImgHTMLAttributes<HTMLImageElement>;
231
+ input: InputHTMLAttributes<HTMLInputElement>;
232
+ label: LabelHTMLAttributes<HTMLLabelElement>;
233
+ legend: HTMLAttributes<HTMLLegendElement>;
234
+ li: HTMLAttributes<HTMLLIElement>;
235
+ main: HTMLAttributes<HTMLElement>;
236
+ nav: HTMLAttributes<HTMLElement>;
237
+ ol: HTMLAttributes<HTMLOListElement>;
238
+ option: OptionHTMLAttributes<HTMLOptionElement>;
239
+ p: HTMLAttributes<HTMLParagraphElement>;
240
+ pre: HTMLAttributes<HTMLPreElement>;
241
+ section: HTMLAttributes<HTMLElement>;
242
+ select: SelectHTMLAttributes<HTMLSelectElement>;
243
+ small: HTMLAttributes<HTMLElement>;
244
+ span: HTMLAttributes<HTMLSpanElement>;
245
+ strong: HTMLAttributes<HTMLElement>;
246
+ style: HTMLAttributes<HTMLStyleElement>;
247
+ textarea: TextareaHTMLAttributes<HTMLTextAreaElement>;
248
+ ul: HTMLAttributes<HTMLUListElement>;
249
+
250
+ // SVG
251
+ svg: SVGAttributes<SVGSVGElement>;
252
+ circle: SVGAttributes<SVGCircleElement>;
253
+ line: SVGAttributes<SVGLineElement>;
254
+ path: SVGAttributes<SVGPathElement>;
255
+ rect: SVGAttributes<SVGRectElement>;
256
+ }
257
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observation.test.d.ts","sourceRoot":"","sources":["../src/observation.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,150 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { Signal, Observer, getCurrentObserver } from './observation';
3
+ describe('Signal', () => {
4
+ it('should allow subscribing to notifications', () => {
5
+ const signal = new Signal();
6
+ const callback = vi.fn();
7
+ signal.subscribe(callback);
8
+ signal.notify();
9
+ expect(callback).toHaveBeenCalledTimes(1);
10
+ });
11
+ it('should return a disposer function', () => {
12
+ const signal = new Signal();
13
+ const callback = vi.fn();
14
+ const dispose = signal.subscribe(callback);
15
+ dispose();
16
+ signal.notify();
17
+ expect(callback).not.toHaveBeenCalled();
18
+ });
19
+ it('should handle multiple subscribers', () => {
20
+ const signal = new Signal();
21
+ const callback1 = vi.fn();
22
+ const callback2 = vi.fn();
23
+ signal.subscribe(callback1);
24
+ signal.subscribe(callback2);
25
+ signal.notify();
26
+ expect(callback1).toHaveBeenCalledTimes(1);
27
+ expect(callback2).toHaveBeenCalledTimes(1);
28
+ });
29
+ it('should allow unsubscribing individual callbacks', () => {
30
+ const signal = new Signal();
31
+ const callback1 = vi.fn();
32
+ const callback2 = vi.fn();
33
+ const dispose1 = signal.subscribe(callback1);
34
+ signal.subscribe(callback2);
35
+ dispose1();
36
+ signal.notify();
37
+ expect(callback1).not.toHaveBeenCalled();
38
+ expect(callback2).toHaveBeenCalledTimes(1);
39
+ });
40
+ });
41
+ describe('Observer', () => {
42
+ it('should queue notifications in microtasks', async () => {
43
+ let callCount = 0;
44
+ const observer = new Observer(() => {
45
+ callCount++;
46
+ });
47
+ const signal = new Signal();
48
+ const dispose = observer.observe();
49
+ observer.subscribeSignal(signal);
50
+ dispose();
51
+ // Trigger multiple notifications
52
+ signal.notify();
53
+ signal.notify();
54
+ signal.notify();
55
+ // Should not be called synchronously
56
+ expect(callCount).toBe(0);
57
+ // Wait for microtask
58
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
59
+ // Should be called only once due to queuing
60
+ expect(callCount).toBe(1);
61
+ });
62
+ it('should track signals during observation', () => {
63
+ const callback = vi.fn();
64
+ const observer = new Observer(callback);
65
+ const signal = new Signal();
66
+ const dispose = observer.observe();
67
+ observer.subscribeSignal(signal);
68
+ dispose();
69
+ signal.notify();
70
+ return new Promise((resolve) => {
71
+ queueMicrotask(() => {
72
+ expect(callback).toHaveBeenCalledTimes(1);
73
+ resolve(undefined);
74
+ });
75
+ });
76
+ });
77
+ it('should clear signals when observing again', async () => {
78
+ let callCount = 0;
79
+ const observer = new Observer(() => {
80
+ callCount++;
81
+ });
82
+ const signal1 = new Signal();
83
+ const signal2 = new Signal();
84
+ // First observation
85
+ let dispose = observer.observe();
86
+ observer.subscribeSignal(signal1);
87
+ dispose();
88
+ // Second observation - should clear previous signals
89
+ dispose = observer.observe();
90
+ observer.subscribeSignal(signal2);
91
+ dispose();
92
+ // Notify first signal - should not trigger observer
93
+ signal1.notify();
94
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
95
+ expect(callCount).toBe(0);
96
+ // Notify second signal - should trigger observer
97
+ signal2.notify();
98
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
99
+ expect(callCount).toBe(1);
100
+ });
101
+ it('should dispose of all signal subscriptions', async () => {
102
+ const callback = vi.fn();
103
+ const observer = new Observer(callback);
104
+ const signal = new Signal();
105
+ const dispose = observer.observe();
106
+ observer.subscribeSignal(signal);
107
+ dispose();
108
+ observer.dispose();
109
+ signal.notify();
110
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
111
+ expect(callback).not.toHaveBeenCalled();
112
+ });
113
+ it('should set current observer during observation', () => {
114
+ const observer = new Observer(() => { });
115
+ expect(getCurrentObserver()).toBeUndefined();
116
+ const dispose = observer.observe();
117
+ expect(getCurrentObserver()).toBe(observer);
118
+ dispose();
119
+ expect(getCurrentObserver()).toBeUndefined();
120
+ });
121
+ it('should handle nested observations with stack', () => {
122
+ const observer1 = new Observer(() => { });
123
+ const observer2 = new Observer(() => { });
124
+ const dispose1 = observer1.observe();
125
+ expect(getCurrentObserver()).toBe(observer1);
126
+ const dispose2 = observer2.observe();
127
+ expect(getCurrentObserver()).toBe(observer2);
128
+ dispose2();
129
+ expect(getCurrentObserver()).toBe(observer1);
130
+ dispose1();
131
+ expect(getCurrentObserver()).toBeUndefined();
132
+ });
133
+ it('should prevent duplicate notifications while queued', async () => {
134
+ let callCount = 0;
135
+ const observer = new Observer(() => {
136
+ callCount++;
137
+ });
138
+ const signal = new Signal();
139
+ const dispose = observer.observe();
140
+ observer.subscribeSignal(signal);
141
+ dispose();
142
+ // Rapid-fire notifications
143
+ for (let i = 0; i < 100; i++) {
144
+ signal.notify();
145
+ }
146
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
147
+ // Should only be called once
148
+ expect(callCount).toBe(1);
149
+ });
150
+ });