ripple 0.2.114 → 0.2.116
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.
- package/package.json +1 -1
- package/src/compiler/phases/1-parse/index.js +79 -0
- package/src/compiler/phases/2-analyze/index.js +17 -0
- package/src/compiler/phases/3-transform/client/index.js +109 -26
- package/src/compiler/phases/3-transform/segments.js +168 -9
- package/src/compiler/types/index.d.ts +16 -0
- package/src/runtime/index-client.js +18 -184
- package/src/runtime/internal/client/bindings.js +443 -0
- package/src/runtime/internal/client/index.js +4 -0
- package/src/runtime/map.js +11 -1
- package/src/runtime/set.js +11 -1
- package/tests/client/map.test.ripple +81 -0
- package/tests/client/set.test.ripple +81 -0
- package/types/index.d.ts +100 -39
|
@@ -138,4 +138,85 @@ describe('TrackedMap', () => {
|
|
|
138
138
|
|
|
139
139
|
expect(container.querySelectorAll('pre')[0].textContent).toBe('[["foo",1],["bar",2]]');
|
|
140
140
|
});
|
|
141
|
+
|
|
142
|
+
it('creates empty TrackedMap using #Map() shorthand syntax', () => {
|
|
143
|
+
component MapTest() {
|
|
144
|
+
let map = #Map();
|
|
145
|
+
|
|
146
|
+
<button onClick={() => map.set('a', 1)}>{'add'}</button>
|
|
147
|
+
<pre>{map.size}</pre>
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
render(MapTest);
|
|
151
|
+
|
|
152
|
+
expect(container.querySelector('pre').textContent).toBe('0');
|
|
153
|
+
|
|
154
|
+
const addButton = container.querySelector('button');
|
|
155
|
+
addButton.click();
|
|
156
|
+
flushSync();
|
|
157
|
+
|
|
158
|
+
expect(container.querySelector('pre').textContent).toBe('1');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
it('creates TrackedMap with initial entries using #Map() shorthand syntax', () => {
|
|
163
|
+
component MapTest() {
|
|
164
|
+
let map = #Map([['a', 1], ['b', 2], ['c', 3]]);
|
|
165
|
+
let value = track(() => map.get('b'));
|
|
166
|
+
|
|
167
|
+
<button onClick={() => map.set('b', 10)}>{'update'}</button>
|
|
168
|
+
<pre>{map.size}</pre>
|
|
169
|
+
<pre>{@value}</pre>
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
render(MapTest);
|
|
173
|
+
|
|
174
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('3');
|
|
175
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
176
|
+
|
|
177
|
+
const updateButton = container.querySelector('button');
|
|
178
|
+
updateButton.click();
|
|
179
|
+
flushSync();
|
|
180
|
+
|
|
181
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('10');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('handles all operations with #Map() shorthand syntax', () => {
|
|
185
|
+
component MapTest() {
|
|
186
|
+
let map = #Map([['x', 100], ['y', 200]]);
|
|
187
|
+
let keys = track(() => Array.from(map.keys()));
|
|
188
|
+
|
|
189
|
+
<button onClick={() => map.set('z', 300)}>{'add'}</button>
|
|
190
|
+
<button onClick={() => map.delete('x')}>{'delete'}</button>
|
|
191
|
+
<button onClick={() => map.clear()}>{'clear'}</button>
|
|
192
|
+
|
|
193
|
+
<pre>{JSON.stringify(@keys)}</pre>
|
|
194
|
+
<pre>{map.size}</pre>
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
render(MapTest);
|
|
198
|
+
|
|
199
|
+
const [addButton, deleteButton, clearButton] = container.querySelectorAll('button');
|
|
200
|
+
|
|
201
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["x","y"]');
|
|
202
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
203
|
+
|
|
204
|
+
addButton.click();
|
|
205
|
+
flushSync();
|
|
206
|
+
|
|
207
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["x","y","z"]');
|
|
208
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
|
|
209
|
+
|
|
210
|
+
deleteButton.click();
|
|
211
|
+
flushSync();
|
|
212
|
+
|
|
213
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["y","z"]');
|
|
214
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
215
|
+
|
|
216
|
+
clearButton.click();
|
|
217
|
+
flushSync();
|
|
218
|
+
|
|
219
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('[]');
|
|
220
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
|
|
221
|
+
});
|
|
141
222
|
});
|
|
@@ -96,4 +96,85 @@ describe('TrackedSet', () => {
|
|
|
96
96
|
|
|
97
97
|
expect(container.querySelectorAll('pre')[0].textContent).toBe('false');
|
|
98
98
|
});
|
|
99
|
+
|
|
100
|
+
it('creates empty TrackedSet using #Set() shorthand syntax', () => {
|
|
101
|
+
component SetTest() {
|
|
102
|
+
let items = #Set();
|
|
103
|
+
|
|
104
|
+
<button onClick={() => items.add(1)}>{'add'}</button>
|
|
105
|
+
<pre>{items.size}</pre>
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
render(SetTest);
|
|
109
|
+
|
|
110
|
+
expect(container.querySelector('pre').textContent).toBe('0');
|
|
111
|
+
|
|
112
|
+
const addButton = container.querySelector('button');
|
|
113
|
+
addButton.click();
|
|
114
|
+
flushSync();
|
|
115
|
+
|
|
116
|
+
expect(container.querySelector('pre').textContent).toBe('1');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('creates TrackedSet with initial values using #Set() shorthand syntax', () => {
|
|
120
|
+
component SetTest() {
|
|
121
|
+
let items = #Set([1, 2, 3, 4]);
|
|
122
|
+
let hasValue = track(() => items.has(3));
|
|
123
|
+
|
|
124
|
+
<button onClick={() => items.delete(3)}>{'delete'}</button>
|
|
125
|
+
<pre>{items.size}</pre>
|
|
126
|
+
<pre>{@hasValue}</pre>
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
render(SetTest);
|
|
130
|
+
|
|
131
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('4');
|
|
132
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('true');
|
|
133
|
+
|
|
134
|
+
const deleteButton = container.querySelector('button');
|
|
135
|
+
deleteButton.click();
|
|
136
|
+
flushSync();
|
|
137
|
+
|
|
138
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('3');
|
|
139
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('false');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('handles all operations with #Set() shorthand syntax', () => {
|
|
143
|
+
component SetTest() {
|
|
144
|
+
let items = #Set([10, 20, 30]);
|
|
145
|
+
let values = track(() => Array.from(items.values()));
|
|
146
|
+
|
|
147
|
+
<button onClick={() => items.add(40)}>{'add'}</button>
|
|
148
|
+
<button onClick={() => items.delete(20)}>{'delete'}</button>
|
|
149
|
+
<button onClick={() => items.clear()}>{'clear'}</button>
|
|
150
|
+
|
|
151
|
+
<pre>{JSON.stringify(@values)}</pre>
|
|
152
|
+
<pre>{items.size}</pre>
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
render(SetTest);
|
|
156
|
+
|
|
157
|
+
const [addButton, deleteButton, clearButton] = container.querySelectorAll('button');
|
|
158
|
+
|
|
159
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('[10,20,30]');
|
|
160
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
|
|
161
|
+
|
|
162
|
+
addButton.click();
|
|
163
|
+
flushSync();
|
|
164
|
+
|
|
165
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('[10,20,30,40]');
|
|
166
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
|
|
167
|
+
|
|
168
|
+
deleteButton.click();
|
|
169
|
+
flushSync();
|
|
170
|
+
|
|
171
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('[10,30,40]');
|
|
172
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
|
|
173
|
+
|
|
174
|
+
clearButton.click();
|
|
175
|
+
flushSync();
|
|
176
|
+
|
|
177
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('[]');
|
|
178
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
|
|
179
|
+
});
|
|
99
180
|
});
|
package/types/index.d.ts
CHANGED
|
@@ -14,13 +14,13 @@ export declare function flushSync<T>(fn: () => T): T;
|
|
|
14
14
|
export declare function effect(fn: (() => void) | (() => () => void)): void;
|
|
15
15
|
|
|
16
16
|
export interface TrackedArrayConstructor {
|
|
17
|
-
new <T>(...elements: T[]): TrackedArray<T>;
|
|
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
|
|
|
@@ -73,24 +73,50 @@ declare global {
|
|
|
73
73
|
|
|
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> {
|
|
78
|
+
'#v': V;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Augment Tracked to be callable when V is a Component
|
|
82
|
+
// This allows <@Something /> to work in JSX when Something is Tracked<Component>
|
|
83
|
+
export interface Tracked<V> {
|
|
84
|
+
(props: V extends Component<infer P> ? P : never): V extends Component ? void : never;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Helper type to infer component type from a function that returns a component
|
|
88
|
+
// If T is a function returning a Component, extract the Component type itself, not the return type (void)
|
|
89
|
+
export type InferComponent<T> = T extends () => infer R ? (R extends Component<any> ? R : T) : T;
|
|
77
90
|
|
|
78
91
|
export type Props<K extends PropertyKey = any, V = unknown> = Record<K, V>;
|
|
79
92
|
export type PropsWithExtras<T extends object> = Props & T & Record<string, unknown>;
|
|
80
|
-
export type PropsWithChildren<T extends object = {}> =
|
|
81
|
-
|
|
93
|
+
export type PropsWithChildren<T extends object = {}> = Expand<
|
|
94
|
+
Omit<Props, 'children'> & { children: Component } & T
|
|
95
|
+
>;
|
|
82
96
|
|
|
83
97
|
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
|
84
98
|
|
|
85
|
-
type PickKeys<T, K extends readonly (keyof T)[]> =
|
|
86
|
-
{ [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]> };
|
|
87
100
|
|
|
88
101
|
type RestKeys<T, K extends readonly (keyof T)[]> = Expand<Omit<T, K[number]>>;
|
|
89
102
|
|
|
90
|
-
type SplitResult<T extends Props, K extends readonly (keyof T)[]> =
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
type SplitResult<T extends Props, K extends readonly (keyof T)[]> = [
|
|
104
|
+
...PickKeys<T, K>,
|
|
105
|
+
Tracked<RestKeys<T, K>>,
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
// Overload for function values - infers the return type of the function
|
|
109
|
+
export declare function track<V>(
|
|
110
|
+
value: () => V,
|
|
111
|
+
get?: (v: InferComponent<V>) => InferComponent<V>,
|
|
112
|
+
set?: (next: InferComponent<V>, prev: InferComponent<V>) => InferComponent<V>,
|
|
113
|
+
): Tracked<InferComponent<V>>;
|
|
114
|
+
// Overload for non-function values
|
|
115
|
+
export declare function track<V>(
|
|
116
|
+
value?: V,
|
|
117
|
+
get?: (v: V) => V,
|
|
118
|
+
set?: (next: V, prev: V) => V,
|
|
119
|
+
): Tracked<V>;
|
|
94
120
|
|
|
95
121
|
export declare function trackSplit<V extends Props, const K extends readonly (keyof V)[]>(
|
|
96
122
|
value: V,
|
|
@@ -101,60 +127,67 @@ export function on<Type extends keyof WindowEventMap>(
|
|
|
101
127
|
window: Window,
|
|
102
128
|
type: Type,
|
|
103
129
|
handler: (this: Window, event: WindowEventMap[Type]) => any,
|
|
104
|
-
options?: AddEventListenerOptions | undefined
|
|
130
|
+
options?: AddEventListenerOptions | undefined,
|
|
105
131
|
): () => void;
|
|
106
132
|
|
|
107
133
|
export function on<Type extends keyof DocumentEventMap>(
|
|
108
134
|
document: Document,
|
|
109
135
|
type: Type,
|
|
110
136
|
handler: (this: Document, event: DocumentEventMap[Type]) => any,
|
|
111
|
-
options?: AddEventListenerOptions | undefined
|
|
137
|
+
options?: AddEventListenerOptions | undefined,
|
|
112
138
|
): () => void;
|
|
113
139
|
|
|
114
140
|
export function on<Element extends HTMLElement, Type extends keyof HTMLElementEventMap>(
|
|
115
141
|
element: Element,
|
|
116
142
|
type: Type,
|
|
117
143
|
handler: (this: Element, event: HTMLElementEventMap[Type]) => any,
|
|
118
|
-
options?: AddEventListenerOptions | undefined
|
|
144
|
+
options?: AddEventListenerOptions | undefined,
|
|
119
145
|
): () => void;
|
|
120
146
|
|
|
121
147
|
export function on<Element extends MediaQueryList, Type extends keyof MediaQueryListEventMap>(
|
|
122
148
|
element: Element,
|
|
123
149
|
type: Type,
|
|
124
150
|
handler: (this: Element, event: MediaQueryListEventMap[Type]) => any,
|
|
125
|
-
options?: AddEventListenerOptions | undefined
|
|
151
|
+
options?: AddEventListenerOptions | undefined,
|
|
126
152
|
): () => void;
|
|
127
153
|
|
|
128
154
|
export function on(
|
|
129
155
|
element: EventTarget,
|
|
130
156
|
type: string,
|
|
131
157
|
handler: EventListener,
|
|
132
|
-
options?: AddEventListenerOptions | undefined
|
|
158
|
+
options?: AddEventListenerOptions | undefined,
|
|
133
159
|
): () => void;
|
|
134
160
|
|
|
135
161
|
export type TrackedObjectShallow<T> = {
|
|
136
162
|
[K in keyof T]: T[K] | Tracked<T[K]>;
|
|
137
163
|
};
|
|
138
164
|
|
|
139
|
-
export type TrackedObjectDeep<T> =
|
|
140
|
-
|
|
165
|
+
export type TrackedObjectDeep<T> = T extends
|
|
166
|
+
| string
|
|
167
|
+
| number
|
|
168
|
+
| boolean
|
|
169
|
+
| null
|
|
170
|
+
| undefined
|
|
171
|
+
| symbol
|
|
172
|
+
| bigint
|
|
141
173
|
? T | Tracked<T>
|
|
142
174
|
: T extends TrackedArray<infer U>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
? TrackedArray<U> | Tracked<TrackedArray<U>>
|
|
176
|
+
: T extends TrackedSet<infer U>
|
|
177
|
+
? TrackedSet<U> | Tracked<TrackedSet<U>>
|
|
178
|
+
: T extends TrackedMap<infer K, infer V>
|
|
179
|
+
? TrackedMap<K, V> | Tracked<TrackedMap<K, V>>
|
|
180
|
+
: T extends Array<infer U>
|
|
181
|
+
? Array<TrackedObjectDeep<U>> | Tracked<Array<TrackedObjectDeep<U>>>
|
|
182
|
+
: T extends Set<infer U>
|
|
183
|
+
? Set<TrackedObjectDeep<U>> | Tracked<Set<TrackedObjectDeep<U>>>
|
|
184
|
+
: T extends Map<infer K, infer V>
|
|
185
|
+
?
|
|
186
|
+
| Map<TrackedObjectDeep<K>, TrackedObjectDeep<V>>
|
|
187
|
+
| Tracked<Map<TrackedObjectDeep<K>, TrackedObjectDeep<V>>>
|
|
188
|
+
: T extends object
|
|
189
|
+
? { [K in keyof T]: TrackedObjectDeep<T[K]> | Tracked<TrackedObjectDeep<T[K]>> }
|
|
190
|
+
: T | Tracked<T>;
|
|
158
191
|
|
|
159
192
|
export type TrackedObject<T extends object> = T & {};
|
|
160
193
|
|
|
@@ -184,31 +217,59 @@ export class TrackedURL extends URL {
|
|
|
184
217
|
export function createSubscriber(start: () => void | (() => void)): () => void;
|
|
185
218
|
|
|
186
219
|
interface ReactiveValue<V> extends Tracked<V> {
|
|
187
|
-
new(fn: () => Tracked<V>, start: () => void | (() => void)): Tracked<V>;
|
|
220
|
+
new (fn: () => Tracked<V>, start: () => void | (() => void)): Tracked<V>;
|
|
188
221
|
/** @private */
|
|
189
222
|
_brand: void;
|
|
190
223
|
}
|
|
191
224
|
|
|
192
225
|
export interface MediaQuery extends Tracked<boolean> {
|
|
193
|
-
new(query: string, fallback?: boolean | undefined): Tracked<boolean>;
|
|
226
|
+
new (query: string, fallback?: boolean | undefined): Tracked<boolean>;
|
|
194
227
|
/** @private */
|
|
195
228
|
_brand: void;
|
|
196
229
|
}
|
|
197
230
|
|
|
198
231
|
export declare const MediaQuery: {
|
|
199
|
-
new(query: string, fallback?: boolean | undefined): Tracked<boolean>;
|
|
232
|
+
new (query: string, fallback?: boolean | undefined): Tracked<boolean>;
|
|
200
233
|
};
|
|
201
234
|
|
|
202
|
-
export function Portal<V = HTMLElement>({
|
|
235
|
+
export function Portal<V = HTMLElement>({
|
|
236
|
+
target,
|
|
237
|
+
children: Component,
|
|
238
|
+
}: {
|
|
239
|
+
target: V;
|
|
240
|
+
children?: Component;
|
|
241
|
+
}): void;
|
|
203
242
|
|
|
204
243
|
/**
|
|
205
244
|
* @param {Tracked<V>} tracked
|
|
206
245
|
* @returns {(node: HTMLInputElement | HTMLSelectElement) => void}
|
|
207
246
|
*/
|
|
208
|
-
export declare function bindValue<V>(
|
|
247
|
+
export declare function bindValue<V>(
|
|
248
|
+
tracked: Tracked<V>,
|
|
249
|
+
): (node: HTMLInputElement | HTMLSelectElement) => void;
|
|
209
250
|
|
|
210
251
|
/**
|
|
211
252
|
* @param {Tracked<V>} tracked
|
|
212
253
|
* @returns {(node: HTMLInputElement) => void}
|
|
213
254
|
*/
|
|
214
|
-
export declare function bindChecked<V>(tracked: Tracked<V>): (node: HTMLInputElement) => void;
|
|
255
|
+
export declare function bindChecked<V>(tracked: Tracked<V>): (node: HTMLInputElement) => void;
|
|
256
|
+
|
|
257
|
+
export declare function bindClientWidth<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
|
|
258
|
+
|
|
259
|
+
export declare function bindClientHeight<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
|
|
260
|
+
|
|
261
|
+
export declare function bindContentRect<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
|
|
262
|
+
|
|
263
|
+
export declare function bindContentBoxSize<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
|
|
264
|
+
|
|
265
|
+
export declare function bindBorderBoxSize<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
|
|
266
|
+
|
|
267
|
+
export declare function bindDevicePixelContentBoxSize<V>(
|
|
268
|
+
tracked: Tracked<V>,
|
|
269
|
+
): (node: HTMLElement) => void;
|
|
270
|
+
|
|
271
|
+
export declare function bindInnerHTML<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
|
|
272
|
+
|
|
273
|
+
export declare function bindInnerText<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
|
|
274
|
+
|
|
275
|
+
export declare function bindTextContent<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
|