ripple 0.2.115 → 0.2.118

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 (93) hide show
  1. package/package.json +16 -16
  2. package/src/compiler/index.js +20 -1
  3. package/src/compiler/phases/1-parse/index.js +79 -0
  4. package/src/compiler/phases/3-transform/client/index.js +54 -8
  5. package/src/compiler/phases/3-transform/segments.js +107 -60
  6. package/src/compiler/phases/3-transform/server/index.js +21 -11
  7. package/src/compiler/types/index.d.ts +16 -0
  8. package/src/runtime/index-client.js +19 -185
  9. package/src/runtime/index-server.js +24 -0
  10. package/src/runtime/internal/client/bindings.js +443 -0
  11. package/src/runtime/internal/client/index.js +4 -0
  12. package/src/runtime/internal/client/runtime.js +10 -0
  13. package/src/runtime/internal/client/utils.js +0 -8
  14. package/src/runtime/map.js +11 -1
  15. package/src/runtime/set.js +11 -1
  16. package/tests/client/__snapshots__/for.test.ripple.snap +80 -0
  17. package/tests/client/_etc.test.ripple +5 -0
  18. package/tests/client/array/array.copy-within.test.ripple +120 -0
  19. package/tests/client/array/array.derived.test.ripple +495 -0
  20. package/tests/client/array/array.iteration.test.ripple +115 -0
  21. package/tests/client/array/array.mutations.test.ripple +385 -0
  22. package/tests/client/array/array.static.test.ripple +237 -0
  23. package/tests/client/array/array.to-methods.test.ripple +93 -0
  24. package/tests/client/basic/__snapshots__/basic.attributes.test.ripple.snap +60 -0
  25. package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +106 -0
  26. package/tests/client/basic/__snapshots__/basic.text.test.ripple.snap +49 -0
  27. package/tests/client/basic/basic.attributes.test.ripple +474 -0
  28. package/tests/client/basic/basic.collections.test.ripple +94 -0
  29. package/tests/client/basic/basic.components.test.ripple +225 -0
  30. package/tests/client/basic/basic.errors.test.ripple +126 -0
  31. package/tests/client/basic/basic.events.test.ripple +222 -0
  32. package/tests/client/basic/basic.reactivity.test.ripple +476 -0
  33. package/tests/client/basic/basic.rendering.test.ripple +204 -0
  34. package/tests/client/basic/basic.styling.test.ripple +63 -0
  35. package/tests/client/basic/basic.utilities.test.ripple +25 -0
  36. package/tests/client/boundaries.test.ripple +2 -21
  37. package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +12 -0
  38. package/tests/client/compiler/__snapshots__/compiler.typescript.test.ripple.snap +22 -0
  39. package/tests/client/compiler/compiler.assignments.test.ripple +112 -0
  40. package/tests/client/compiler/compiler.attributes.test.ripple +95 -0
  41. package/tests/client/compiler/compiler.basic.test.ripple +203 -0
  42. package/tests/client/compiler/compiler.regex.test.ripple +87 -0
  43. package/tests/client/compiler/compiler.typescript.test.ripple +29 -0
  44. package/tests/client/{__snapshots__/composite.test.ripple.snap → composite/__snapshots__/composite.render.test.ripple.snap} +2 -2
  45. package/tests/client/composite/composite.dynamic-components.test.ripple +100 -0
  46. package/tests/client/composite/composite.generics.test.ripple +211 -0
  47. package/tests/client/composite/composite.props.test.ripple +106 -0
  48. package/tests/client/composite/composite.reactivity.test.ripple +184 -0
  49. package/tests/client/composite/composite.render.test.ripple +84 -0
  50. package/tests/client/computed-properties.test.ripple +2 -21
  51. package/tests/client/context.test.ripple +5 -22
  52. package/tests/client/date.test.ripple +1 -20
  53. package/tests/client/dynamic-elements.test.ripple +16 -24
  54. package/tests/client/for.test.ripple +4 -23
  55. package/tests/client/head.test.ripple +11 -23
  56. package/tests/client/html.test.ripple +1 -20
  57. package/tests/client/input-value.test.ripple +11 -31
  58. package/tests/client/map.test.ripple +82 -20
  59. package/tests/client/media-query.test.ripple +10 -23
  60. package/tests/client/object.test.ripple +5 -24
  61. package/tests/client/portal.test.ripple +2 -19
  62. package/tests/client/ref.test.ripple +8 -26
  63. package/tests/client/set.test.ripple +84 -22
  64. package/tests/client/svg.test.ripple +1 -22
  65. package/tests/client/switch.test.ripple +6 -25
  66. package/tests/client/tracked-expression.test.ripple +2 -21
  67. package/tests/client/typescript-generics.test.ripple +0 -21
  68. package/tests/client/url/url.derived.test.ripple +83 -0
  69. package/tests/client/url/url.parsing.test.ripple +165 -0
  70. package/tests/client/url/url.partial-removal.test.ripple +198 -0
  71. package/tests/client/url/url.reactivity.test.ripple +449 -0
  72. package/tests/client/url/url.serialization.test.ripple +50 -0
  73. package/tests/client/url-search-params/url-search-params.derived.test.ripple +84 -0
  74. package/tests/client/url-search-params/url-search-params.initialization.test.ripple +61 -0
  75. package/tests/client/url-search-params/url-search-params.iteration.test.ripple +153 -0
  76. package/tests/client/url-search-params/url-search-params.mutation.test.ripple +343 -0
  77. package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +160 -0
  78. package/tests/client/url-search-params/url-search-params.serialization.test.ripple +53 -0
  79. package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +55 -0
  80. package/tests/client.d.ts +12 -0
  81. package/tests/server/if.test.ripple +66 -0
  82. package/tests/setup-client.js +28 -0
  83. package/tsconfig.json +4 -2
  84. package/types/index.d.ts +92 -46
  85. package/LICENSE +0 -21
  86. package/tests/client/__snapshots__/basic.test.ripple.snap +0 -117
  87. package/tests/client/__snapshots__/compiler.test.ripple.snap +0 -33
  88. package/tests/client/array.test.ripple +0 -1455
  89. package/tests/client/basic.test.ripple +0 -1892
  90. package/tests/client/compiler.test.ripple +0 -541
  91. package/tests/client/composite.test.ripple +0 -692
  92. package/tests/client/url-search-params.test.ripple +0 -912
  93. package/tests/client/url.test.ripple +0 -954
@@ -0,0 +1,160 @@
1
+ import { flushSync, track, TrackedURLSearchParams } from 'ripple';
2
+
3
+ describe('TrackedURLSearchParams > retrieval', () => {
4
+ it('handles get operation with reactivity', () => {
5
+ component URLTest() {
6
+ const params = new TrackedURLSearchParams('foo=bar&baz=qux');
7
+ let foo = track(() => params.get('foo'));
8
+ let baz = track(() => params.get('baz'));
9
+
10
+ <button onClick={() => params.set('foo', 'updated')}>{'update foo'}</button>
11
+ <pre>{@foo}</pre>
12
+ <pre>{@baz}</pre>
13
+ }
14
+
15
+ render(URLTest);
16
+
17
+ const button = container.querySelector('button');
18
+
19
+ // Initial state
20
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('bar');
21
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('qux');
22
+
23
+ // Test update
24
+ button.click();
25
+ flushSync();
26
+
27
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('updated');
28
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('qux');
29
+ });
30
+
31
+ it('handles get for nonexistent key', () => {
32
+ component URLTest() {
33
+ const params = new TrackedURLSearchParams('foo=bar');
34
+ let nonexistent = track(() => params.get('nonexistent'));
35
+
36
+ <pre>{String(@nonexistent)}</pre>
37
+ }
38
+
39
+ render(URLTest);
40
+
41
+ expect(container.querySelector('pre').textContent).toBe('null');
42
+ });
43
+
44
+ it('handles getAll operation with reactivity', () => {
45
+ component URLTest() {
46
+ const params = new TrackedURLSearchParams('foo=bar&foo=baz');
47
+ let allFoo = track(() => params.getAll('foo'));
48
+
49
+ <button onClick={() => params.append('foo', 'qux')}>{'append foo'}</button>
50
+ <pre>{JSON.stringify(@allFoo)}</pre>
51
+ }
52
+
53
+ render(URLTest);
54
+
55
+ const button = container.querySelector('button');
56
+
57
+ // Initial state
58
+ expect(container.querySelector('pre').textContent).toBe('["bar","baz"]');
59
+
60
+ // Test append
61
+ button.click();
62
+ flushSync();
63
+
64
+ expect(container.querySelector('pre').textContent).toBe('["bar","baz","qux"]');
65
+ });
66
+
67
+ it('handles has operation with reactivity', () => {
68
+ component URLTest() {
69
+ const params = new TrackedURLSearchParams('foo=bar');
70
+ let hasFoo = track(() => params.has('foo'));
71
+ let hasBaz = track(() => params.has('baz'));
72
+
73
+ <button onClick={() => params.append('baz', 'qux')}>{'add baz'}</button>
74
+ <button onClick={() => params.delete('foo')}>{'delete foo'}</button>
75
+ <pre>{@hasFoo.toString()}</pre>
76
+ <pre>{@hasBaz.toString()}</pre>
77
+ }
78
+
79
+ render(URLTest);
80
+
81
+ const addButton = container.querySelectorAll('button')[0];
82
+ const deleteButton = container.querySelectorAll('button')[1];
83
+
84
+ // Initial state
85
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('true');
86
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('false');
87
+
88
+ // Test add
89
+ addButton.click();
90
+ flushSync();
91
+
92
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('true');
93
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('true');
94
+
95
+ // Test delete
96
+ deleteButton.click();
97
+ flushSync();
98
+
99
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('false');
100
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('true');
101
+ });
102
+
103
+ it('handles has with specific value', () => {
104
+ component URLTest() {
105
+ const params = new TrackedURLSearchParams('foo=bar&foo=baz');
106
+ let hasBarValue = track(() => params.has('foo', 'bar'));
107
+ let hasQuxValue = track(() => params.has('foo', 'qux'));
108
+
109
+ <button onClick={() => params.append('foo', 'qux')}>{'add qux'}</button>
110
+ <pre>{@hasBarValue.toString()}</pre>
111
+ <pre>{@hasQuxValue.toString()}</pre>
112
+ }
113
+
114
+ render(URLTest);
115
+
116
+ const button = container.querySelector('button');
117
+
118
+ // Initial state
119
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('true');
120
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('false');
121
+
122
+ // Test add
123
+ button.click();
124
+ flushSync();
125
+
126
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('true');
127
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('true');
128
+ });
129
+
130
+ it('handles size property with reactivity', () => {
131
+ component URLTest() {
132
+ const params = new TrackedURLSearchParams('foo=bar');
133
+ let size = track(() => params.size);
134
+
135
+ <button onClick={() => params.append('baz', 'qux')}>{'add'}</button>
136
+ <button onClick={() => params.delete('foo')}>{'delete'}</button>
137
+ <pre>{@size}</pre>
138
+ }
139
+
140
+ render(URLTest);
141
+
142
+ const addButton = container.querySelectorAll('button')[0];
143
+ const deleteButton = container.querySelectorAll('button')[1];
144
+
145
+ // Initial state
146
+ expect(container.querySelector('pre').textContent).toBe('1');
147
+
148
+ // Test add
149
+ addButton.click();
150
+ flushSync();
151
+
152
+ expect(container.querySelector('pre').textContent).toBe('2');
153
+
154
+ // Test delete
155
+ deleteButton.click();
156
+ flushSync();
157
+
158
+ expect(container.querySelector('pre').textContent).toBe('1');
159
+ });
160
+ });
@@ -0,0 +1,53 @@
1
+ import { flushSync, track, TrackedURLSearchParams } from 'ripple';
2
+
3
+ describe('TrackedURLSearchParams > serialization', () => {
4
+ it('handles toString method with reactivity', () => {
5
+ component URLTest() {
6
+ const params = new TrackedURLSearchParams('foo=bar');
7
+ let string = track(() => params.toString());
8
+
9
+ <button onClick={() => params.append('baz', 'qux')}>{'add'}</button>
10
+ <pre>{@string}</pre>
11
+ }
12
+
13
+ render(URLTest);
14
+
15
+ const button = container.querySelector('button');
16
+
17
+ // Initial state
18
+ expect(container.querySelector('pre').textContent).toBe('foo=bar');
19
+
20
+ // Test add
21
+ button.click();
22
+ flushSync();
23
+
24
+ expect(container.querySelector('pre').textContent).toBe('foo=bar&baz=qux');
25
+ });
26
+
27
+ it('handles special characters encoding', () => {
28
+ component URLTest() {
29
+ const params = new TrackedURLSearchParams();
30
+
31
+ <button onClick={() => params.set('key', 'value with spaces')}>{'add spaces'}</button>
32
+ <button onClick={() => params.set('special', '!@#$%^&*()')}>{'add special'}</button>
33
+ <pre>{params.toString()}</pre>
34
+ }
35
+
36
+ render(URLTest);
37
+
38
+ const spacesButton = container.querySelectorAll('button')[0];
39
+ const specialButton = container.querySelectorAll('button')[1];
40
+
41
+ // Test spaces
42
+ spacesButton.click();
43
+ flushSync();
44
+
45
+ expect(container.querySelector('pre').textContent).toBe('key=value+with+spaces');
46
+
47
+ // Test special characters
48
+ specialButton.click();
49
+ flushSync();
50
+
51
+ expect(container.querySelector('pre').textContent).toContain('special');
52
+ });
53
+ });
@@ -0,0 +1,55 @@
1
+ import { flushSync, TrackedURL } from 'ripple';
2
+
3
+ describe('TrackedURLSearchParams > TrackedURL integration', () => {
4
+ it('integrates with TrackedURL', () => {
5
+ component URLTest() {
6
+ const url = new TrackedURL('https://example.com?foo=bar');
7
+ const params = url.searchParams;
8
+
9
+ <button onClick={() => params.append('baz', 'qux')}>{'add param'}</button>
10
+ <pre>{url.href}</pre>
11
+ <pre>{params.toString()}</pre>
12
+ }
13
+
14
+ render(URLTest);
15
+
16
+ const button = container.querySelector('button');
17
+
18
+ // Initial state
19
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/?foo=bar');
20
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('foo=bar');
21
+
22
+ // Test add param - should update URL
23
+ button.click();
24
+ flushSync();
25
+
26
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/?foo=bar&baz=qux');
27
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('foo=bar&baz=qux');
28
+ });
29
+
30
+ it('handles empty search string in URL', () => {
31
+ component URLTest() {
32
+ const url = new TrackedURL('https://example.com');
33
+ const params = url.searchParams;
34
+
35
+ <button onClick={() => params.append('foo', 'bar')}>{'add first param'}</button>
36
+ <pre>{url.href}</pre>
37
+ <pre>{params.size}</pre>
38
+ }
39
+
40
+ render(URLTest);
41
+
42
+ const button = container.querySelector('button');
43
+
44
+ // Initial state - no search params
45
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/');
46
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
47
+
48
+ // Test add first param
49
+ button.click();
50
+ flushSync();
51
+
52
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/?foo=bar');
53
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
54
+ });
55
+ });
@@ -0,0 +1,12 @@
1
+ declare var container: HTMLDivElement;
2
+ declare var error: string | undefined;
3
+ declare function render(component: () => void): void;
4
+
5
+ interface HTMLElement {
6
+ // We don't care about checking if it returned an element or null in tests
7
+ // because if it returned null, those tests will fail anyway. This
8
+ // typing drastically simplifies testing: you don't have to check if the
9
+ // query returned null or an actual element, and you don't have to do
10
+ // optional chaining everywhere (elem?.textContent)
11
+ querySelector(selectors: string): HTMLElement;
12
+ }
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render } from 'ripple/server';
3
+
4
+ describe('if statements in SSR', () => {
5
+ it('renders if block when condition is true', async () => {
6
+ component App() {
7
+ let condition = true;
8
+
9
+ if (condition) {
10
+ <div>{'If block'}</div>
11
+ }
12
+ }
13
+
14
+ const { body } = await render(App);
15
+ expect(body).toBe('<div>If block</div>');
16
+ });
17
+
18
+ it('renders else block when condition is false', async () => {
19
+ component App() {
20
+ let condition = false;
21
+
22
+ if (condition) {
23
+ <div>{'If block'}</div>
24
+ } else {
25
+ <div>{'Else block'}</div>
26
+ }
27
+ }
28
+
29
+ const { body } = await render(App);
30
+ expect(body).toBe('<div>Else block</div>');
31
+ });
32
+
33
+ it('renders else if block when condition is true', async () => {
34
+ component App() {
35
+ let value = 'b';
36
+
37
+ if (value === 'a') {
38
+ <div>{'Case A'}</div>
39
+ } else if (value === 'b') {
40
+ <div>{'Case B'}</div>
41
+ } else {
42
+ <div>{'Default Case'}</div>
43
+ }
44
+ }
45
+
46
+ const { body } = await render(App);
47
+ expect(body).toBe('<div>Case B</div>');
48
+ });
49
+
50
+ it('renders final else block in an if-else if-else chain', async () => {
51
+ component App() {
52
+ let value = 'c';
53
+
54
+ if (value === 'a') {
55
+ <div>{'Case A'}</div>
56
+ } else if (value === 'b') {
57
+ <div>{'Case B'}</div>
58
+ } else {
59
+ <div>{'Default Case'}</div>
60
+ }
61
+ }
62
+
63
+ const { body } = await render(App);
64
+ expect(body).toBe('<div>Default Case</div>');
65
+ });
66
+ });
@@ -0,0 +1,28 @@
1
+ import { beforeEach, afterEach } from 'vitest';
2
+ import { mount } from 'ripple';
3
+
4
+ /**
5
+ * @param {() => void} component
6
+ */
7
+ globalThis.render = function render(component) {
8
+ mount(component, {
9
+ target: /** @type {HTMLDivElement} */ (globalThis.container)
10
+ });
11
+ }
12
+
13
+ beforeEach(() => {
14
+ globalThis.container = /** @type {HTMLDivElement} */ (document.createElement('div'));
15
+ document.body.appendChild(globalThis.container);
16
+
17
+ globalThis.error = undefined;
18
+ });
19
+
20
+ afterEach(() => {
21
+ // Container is guaranteed to exist in all tests, so it was easier to type it without undefined.
22
+ // And when we unset it, we just type-cast it to HTMLDivElement to avoid TS errors, because we
23
+ // know it's guaranteed to exist in the next test again.
24
+ document.body.removeChild(/** @type {HTMLDivElement} */ (globalThis.container));
25
+ globalThis.container = /** @type {HTMLDivElement} */ (/** @type {unknown} */(undefined));
26
+
27
+ globalThis.error = undefined;
28
+ });
package/tsconfig.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "noErrorTruncation": true,
11
11
  "allowSyntheticDefaultImports": true,
12
12
  "verbatimModuleSyntax": true,
13
- "types": ["node"],
13
+ "types": ["node", "vitest/globals"],
14
14
  "jsx": "preserve",
15
15
  "jsxImportSource": "ripple",
16
16
  "strict": true,
@@ -23,6 +23,8 @@
23
23
  "include": [
24
24
  "./*.js",
25
25
  "./src/",
26
- "./tests/*/test.ripple",
26
+ "./tests/**/*.test.ripple",
27
+ "./tests/**/*.d.ts",
28
+ "./tests/**/*.js"
27
29
  ]
28
30
  }
package/types/index.d.ts CHANGED
@@ -9,18 +9,18 @@ export declare function tick(): Promise<void>;
9
9
 
10
10
  export declare function untrack<T>(fn: () => T): T;
11
11
 
12
- export declare function flushSync<T>(fn: () => T): T;
12
+ export declare function flushSync<T>(fn?: () => T): T;
13
13
 
14
14
  export declare function effect(fn: (() => void) | (() => () => void)): void;
15
15
 
16
16
  export interface TrackedArrayConstructor {
17
- new <T>(...elements: T[]): TrackedArray<T>; // must be used with `new`
17
+ new <T>(...elements: T[]): TrackedArray<T>; // must be used with `new`
18
18
  from<T>(arrayLike: ArrayLike<T>): TrackedArray<T>;
19
19
  of<T>(...items: T[]): TrackedArray<T>;
20
20
  fromAsync<T>(iterable: AsyncIterable<T>): Promise<TrackedArray<T>>;
21
21
  }
22
22
 
23
- export interface TrackedArray<T> extends Array<T> { }
23
+ export interface TrackedArray<T> extends Array<T> {}
24
24
 
25
25
  export declare const TrackedArray: TrackedArrayConstructor;
26
26
 
@@ -74,7 +74,9 @@ declare global {
74
74
  export declare function createRefKey(): symbol;
75
75
 
76
76
  // Base Tracked interface - all tracked values have a '#v' property containing the actual value
77
- export interface Tracked<V> { '#v': V; }
77
+ export interface Tracked<V> {
78
+ '#v': V;
79
+ }
78
80
 
79
81
  // Augment Tracked to be callable when V is a Component
80
82
  // This allows <@Something /> to work in JSX when Something is Tracked<Component>
@@ -84,32 +86,41 @@ export interface Tracked<V> {
84
86
 
85
87
  // Helper type to infer component type from a function that returns a component
86
88
  // If T is a function returning a Component, extract the Component type itself, not the return type (void)
87
- export type InferComponent<T> =
88
- T extends () => infer R
89
- ? R extends Component<any>
90
- ? R
91
- : T
92
- : T;
89
+ export type InferComponent<T> = T extends () => infer R ? (R extends Component<any> ? R : T) : T;
93
90
 
94
91
  export type Props<K extends PropertyKey = any, V = unknown> = Record<K, V>;
95
92
  export type PropsWithExtras<T extends object> = Props & T & Record<string, unknown>;
96
- export type PropsWithChildren<T extends object = {}> =
97
- Expand<Omit<Props, 'children'> & { children: Component } & T>;
93
+ export type PropsWithChildren<T extends object = {}> = Expand<
94
+ Omit<Props, 'children'> & { children: Component } & T
95
+ >;
98
96
 
99
97
  type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
100
98
 
101
- type PickKeys<T, K extends readonly (keyof T)[]> =
102
- { [I in keyof K]: Tracked<T[K[I] & keyof T]> };
99
+ type PickKeys<T, K extends readonly (keyof T)[]> = { [I in keyof K]: Tracked<T[K[I] & keyof T]> };
103
100
 
104
101
  type RestKeys<T, K extends readonly (keyof T)[]> = Expand<Omit<T, K[number]>>;
105
102
 
106
- type SplitResult<T extends Props, K extends readonly (keyof T)[]> =
107
- [...PickKeys<T, K>, Tracked<RestKeys<T, K>>];
103
+ type SplitResult<T extends Props, K extends readonly (keyof T)[]> = [
104
+ ...PickKeys<T, K>,
105
+ Tracked<RestKeys<T, K>>,
106
+ ];
107
+
108
+ export declare function get<V>(tracked: Tracked<V>): V;
109
+
110
+ export declare function set<V>(tracked: Tracked<V>, value: V): void;
108
111
 
109
112
  // Overload for function values - infers the return type of the function
110
- export declare function track<V>(value: () => V, get?: (v: InferComponent<V>) => InferComponent<V>, set?: (next: InferComponent<V>, prev: InferComponent<V>) => InferComponent<V>): Tracked<InferComponent<V>>;
113
+ export declare function track<V>(
114
+ value: () => V,
115
+ get?: (v: InferComponent<V>) => InferComponent<V>,
116
+ set?: (next: InferComponent<V>, prev: InferComponent<V>) => InferComponent<V>,
117
+ ): Tracked<InferComponent<V>>;
111
118
  // Overload for non-function values
112
- export declare function track<V>(value?: V, get?: (v: V) => V, set?: (next: V, prev: V) => V): Tracked<V>;
119
+ export declare function track<V>(
120
+ value?: V,
121
+ get?: (v: V) => V,
122
+ set?: (next: V, prev: V) => V,
123
+ ): Tracked<V>;
113
124
 
114
125
  export declare function trackSplit<V extends Props, const K extends readonly (keyof V)[]>(
115
126
  value: V,
@@ -120,60 +131,67 @@ export function on<Type extends keyof WindowEventMap>(
120
131
  window: Window,
121
132
  type: Type,
122
133
  handler: (this: Window, event: WindowEventMap[Type]) => any,
123
- options?: AddEventListenerOptions | undefined
134
+ options?: AddEventListenerOptions | undefined,
124
135
  ): () => void;
125
136
 
126
137
  export function on<Type extends keyof DocumentEventMap>(
127
138
  document: Document,
128
139
  type: Type,
129
140
  handler: (this: Document, event: DocumentEventMap[Type]) => any,
130
- options?: AddEventListenerOptions | undefined
141
+ options?: AddEventListenerOptions | undefined,
131
142
  ): () => void;
132
143
 
133
144
  export function on<Element extends HTMLElement, Type extends keyof HTMLElementEventMap>(
134
145
  element: Element,
135
146
  type: Type,
136
147
  handler: (this: Element, event: HTMLElementEventMap[Type]) => any,
137
- options?: AddEventListenerOptions | undefined
148
+ options?: AddEventListenerOptions | undefined,
138
149
  ): () => void;
139
150
 
140
151
  export function on<Element extends MediaQueryList, Type extends keyof MediaQueryListEventMap>(
141
152
  element: Element,
142
153
  type: Type,
143
154
  handler: (this: Element, event: MediaQueryListEventMap[Type]) => any,
144
- options?: AddEventListenerOptions | undefined
155
+ options?: AddEventListenerOptions | undefined,
145
156
  ): () => void;
146
157
 
147
158
  export function on(
148
159
  element: EventTarget,
149
160
  type: string,
150
161
  handler: EventListener,
151
- options?: AddEventListenerOptions | undefined
162
+ options?: AddEventListenerOptions | undefined,
152
163
  ): () => void;
153
164
 
154
165
  export type TrackedObjectShallow<T> = {
155
166
  [K in keyof T]: T[K] | Tracked<T[K]>;
156
167
  };
157
168
 
158
- export type TrackedObjectDeep<T> =
159
- T extends string | number | boolean | null | undefined | symbol | bigint
169
+ export type TrackedObjectDeep<T> = T extends
170
+ | string
171
+ | number
172
+ | boolean
173
+ | null
174
+ | undefined
175
+ | symbol
176
+ | bigint
160
177
  ? T | Tracked<T>
161
178
  : T extends TrackedArray<infer U>
162
- ? TrackedArray<U> | Tracked<TrackedArray<U>>
163
- : T extends TrackedSet<infer U>
164
- ? TrackedSet<U> | Tracked<TrackedSet<U>>
165
- : T extends TrackedMap<infer K, infer V>
166
- ? TrackedMap<K, V> | Tracked<TrackedMap<K, V>>
167
- : T extends Array<infer U>
168
- ? Array<TrackedObjectDeep<U>> | Tracked<Array<TrackedObjectDeep<U>>>
169
- : T extends Set<infer U>
170
- ? Set<TrackedObjectDeep<U>> | Tracked<Set<TrackedObjectDeep<U>>>
171
- : T extends Map<infer K, infer V>
172
- ? Map<TrackedObjectDeep<K>, TrackedObjectDeep<V>> |
173
- Tracked<Map<TrackedObjectDeep<K>, TrackedObjectDeep<V>>>
174
- : T extends object
175
- ? { [K in keyof T]: TrackedObjectDeep<T[K]> | Tracked<TrackedObjectDeep<T[K]>> }
176
- : T | Tracked<T>;
179
+ ? TrackedArray<U> | Tracked<TrackedArray<U>>
180
+ : T extends TrackedSet<infer U>
181
+ ? TrackedSet<U> | Tracked<TrackedSet<U>>
182
+ : T extends TrackedMap<infer K, infer V>
183
+ ? TrackedMap<K, V> | Tracked<TrackedMap<K, V>>
184
+ : T extends Array<infer U>
185
+ ? Array<TrackedObjectDeep<U>> | Tracked<Array<TrackedObjectDeep<U>>>
186
+ : T extends Set<infer U>
187
+ ? Set<TrackedObjectDeep<U>> | Tracked<Set<TrackedObjectDeep<U>>>
188
+ : T extends Map<infer K, infer V>
189
+ ?
190
+ | Map<TrackedObjectDeep<K>, TrackedObjectDeep<V>>
191
+ | Tracked<Map<TrackedObjectDeep<K>, TrackedObjectDeep<V>>>
192
+ : T extends object
193
+ ? { [K in keyof T]: TrackedObjectDeep<T[K]> | Tracked<TrackedObjectDeep<T[K]>> }
194
+ : T | Tracked<T>;
177
195
 
178
196
  export type TrackedObject<T extends object> = T & {};
179
197
 
@@ -203,31 +221,59 @@ export class TrackedURL extends URL {
203
221
  export function createSubscriber(start: () => void | (() => void)): () => void;
204
222
 
205
223
  interface ReactiveValue<V> extends Tracked<V> {
206
- new(fn: () => Tracked<V>, start: () => void | (() => void)): Tracked<V>;
224
+ new (fn: () => Tracked<V>, start: () => void | (() => void)): Tracked<V>;
207
225
  /** @private */
208
226
  _brand: void;
209
227
  }
210
228
 
211
229
  export interface MediaQuery extends Tracked<boolean> {
212
- new(query: string, fallback?: boolean | undefined): Tracked<boolean>;
230
+ new (query: string, fallback?: boolean | undefined): Tracked<boolean>;
213
231
  /** @private */
214
232
  _brand: void;
215
233
  }
216
234
 
217
235
  export declare const MediaQuery: {
218
- new(query: string, fallback?: boolean | undefined): Tracked<boolean>;
236
+ new (query: string, fallback?: boolean | undefined): Tracked<boolean>;
219
237
  };
220
238
 
221
- export function Portal<V = HTMLElement>({ target, children: Component }: { target: V, children?: Component }): void;
239
+ export function Portal<V = HTMLElement>({
240
+ target,
241
+ children: Component,
242
+ }: {
243
+ target: V;
244
+ children?: Component;
245
+ }): void;
222
246
 
223
247
  /**
224
248
  * @param {Tracked<V>} tracked
225
249
  * @returns {(node: HTMLInputElement | HTMLSelectElement) => void}
226
250
  */
227
- export declare function bindValue<V>(tracked: Tracked<V>): (node: HTMLInputElement | HTMLSelectElement) => void;
251
+ export declare function bindValue<V>(
252
+ tracked: Tracked<V>,
253
+ ): (node: HTMLInputElement | HTMLSelectElement) => void;
228
254
 
229
255
  /**
230
256
  * @param {Tracked<V>} tracked
231
257
  * @returns {(node: HTMLInputElement) => void}
232
258
  */
233
- export declare function bindChecked<V>(tracked: Tracked<V>): (node: HTMLInputElement) => void;
259
+ export declare function bindChecked<V>(tracked: Tracked<V>): (node: HTMLInputElement) => void;
260
+
261
+ export declare function bindClientWidth<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
262
+
263
+ export declare function bindClientHeight<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
264
+
265
+ export declare function bindContentRect<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
266
+
267
+ export declare function bindContentBoxSize<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
268
+
269
+ export declare function bindBorderBoxSize<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
270
+
271
+ export declare function bindDevicePixelContentBoxSize<V>(
272
+ tracked: Tracked<V>,
273
+ ): (node: HTMLElement) => void;
274
+
275
+ export declare function bindInnerHTML<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
276
+
277
+ export declare function bindInnerText<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
278
+
279
+ export declare function bindTextContent<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Dominic Gannaway
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.