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.
@@ -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>; // 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
 
@@ -73,24 +73,50 @@ declare global {
73
73
 
74
74
  export declare function createRefKey(): symbol;
75
75
 
76
- export type Tracked<V> = { '#v': V };
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
- Expand<Omit<Props, 'children'> & { children: Component } & T>;
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
- [...PickKeys<T, K>, Tracked<RestKeys<T, K>>];
92
-
93
- export declare function track<V>(value?: V | (() => V), get?: (v: V) => V, set?: (next: V, prev: V) => V): Tracked<V>;
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
- T extends string | number | boolean | null | undefined | symbol | bigint
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
- ? TrackedArray<U> | Tracked<TrackedArray<U>>
144
- : T extends TrackedSet<infer U>
145
- ? TrackedSet<U> | Tracked<TrackedSet<U>>
146
- : T extends TrackedMap<infer K, infer V>
147
- ? TrackedMap<K, V> | Tracked<TrackedMap<K, V>>
148
- : T extends Array<infer U>
149
- ? Array<TrackedObjectDeep<U>> | Tracked<Array<TrackedObjectDeep<U>>>
150
- : T extends Set<infer U>
151
- ? Set<TrackedObjectDeep<U>> | Tracked<Set<TrackedObjectDeep<U>>>
152
- : T extends Map<infer K, infer V>
153
- ? Map<TrackedObjectDeep<K>, TrackedObjectDeep<V>> |
154
- Tracked<Map<TrackedObjectDeep<K>, TrackedObjectDeep<V>>>
155
- : T extends object
156
- ? { [K in keyof T]: TrackedObjectDeep<T[K]> | Tracked<TrackedObjectDeep<T[K]>> }
157
- : T | Tracked<T>;
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>({ target, children: Component }: { target: V, children?: Component }): void;
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>(tracked: Tracked<V>): (node: HTMLInputElement | HTMLSelectElement) => void;
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;