ripple 0.3.83 → 0.3.84

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # ripple
2
2
 
3
+ ## 0.3.84
4
+
5
+ ### Patch Changes
6
+
7
+ - [`a5d1860`](https://github.com/Ripple-TS/ripple/commit/a5d18603beac4b15e99f9d23f4d0b18b67ffe413)
8
+ Thanks [@leonidaz](https://github.com/leonidaz)! - Adds snapshot() api for
9
+ non-reactive shallow copies of RippleArray and RippleObject
10
+ - Updated dependencies
11
+ [[`cc3176b`](https://github.com/Ripple-TS/ripple/commit/cc3176b4e40021021986830bdfa3295530715432),
12
+ [`cc3176b`](https://github.com/Ripple-TS/ripple/commit/cc3176b4e40021021986830bdfa3295530715432)]:
13
+ - @tsrx/core@0.1.32
14
+ - @tsrx/ripple@0.1.32
15
+
3
16
  ## 0.3.83
4
17
 
5
18
  ### Patch Changes
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.3.83",
6
+ "version": "0.3.84",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -73,8 +73,8 @@
73
73
  "clsx": "^2.1.1",
74
74
  "devalue": "^5.8.1",
75
75
  "esm-env": "^1.2.2",
76
- "@tsrx/core": "0.1.31",
77
- "@tsrx/ripple": "0.1.31"
76
+ "@tsrx/core": "0.1.32",
77
+ "@tsrx/ripple": "0.1.32"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@types/estree": "^1.0.8",
@@ -178,6 +178,8 @@ export {
178
178
  peek_tracked as peek,
179
179
  } from './internal/client/runtime.js';
180
180
 
181
+ export { snapshot } from './proxy.js';
182
+
181
183
  export { RippleArray } from './array.js';
182
184
 
183
185
  export { RippleObject } from './object.js';
@@ -17,6 +17,19 @@ export {
17
17
  } from './internal/client/constants.js';
18
18
  export { isRefProp } from '@tsrx/core/runtime/ref';
19
19
 
20
+ /**
21
+ * Server values are never proxied, so a snapshot is just a shallow copy.
22
+ * @template {Iterable<unknown> | object} T
23
+ * @param {T} value
24
+ * @returns {T}
25
+ */
26
+ export function snapshot(value) {
27
+ if (typeof value !== 'object' || value === null) {
28
+ return value;
29
+ }
30
+ return /** @type {T} */ (Array.isArray(value) ? [...value] : { ...value });
31
+ }
32
+
20
33
  export const effect = noop;
21
34
  export const createRefKey = noop;
22
35
  export const on = noop;
@@ -1,7 +1,7 @@
1
1
  /** @import { Block, Tracked } from '#client' */
2
2
  /** @import { RippleArray, RippleObject } from '#public' */
3
3
 
4
- import { get, set, tracked } from './internal/client/runtime.js';
4
+ import { get, set, tracked, untrack } from './internal/client/runtime.js';
5
5
  import {
6
6
  array_prototype,
7
7
  get_descriptor,
@@ -16,6 +16,22 @@ import {
16
16
  UNINITIALIZED,
17
17
  } from './internal/client/constants.js';
18
18
 
19
+ /**
20
+ * Returns a shallow, detached plain copy of a reactive array or object.
21
+ * Values are read without registering any reactivity.
22
+ *
23
+ * @template {Iterable<unknown> | object} T
24
+ * @param {T} value
25
+ * @returns {T}
26
+ */
27
+ export function snapshot(value) {
28
+ if (typeof value !== 'object' || value === null) {
29
+ return value;
30
+ }
31
+
32
+ return untrack(() => /** @type {T} */ (is_array(value) ? [...value] : { ...value }));
33
+ }
34
+
19
35
  /**
20
36
  * @template T
21
37
  * @param {T[] | Record<PropertyKey, any>} value
@@ -1,4 +1,4 @@
1
- import { RippleObject, flushSync } from 'ripple';
1
+ import { RippleObject, effect, flushSync, snapshot } from 'ripple';
2
2
  import { TRACKED_OBJECT } from '../../src/runtime/internal/client/constants.js';
3
3
 
4
4
  describe('RippleObject', () => {
@@ -194,4 +194,50 @@ describe('RippleObject', () => {
194
194
  expect(pre1.textContent).toBe('1');
195
195
  expect(pre3.textContent).toBe('{"a":1,"b":1,"c":{"d":{"e":8}}}');
196
196
  });
197
+
198
+ it('snapshot() returns a detached plain copy of current values', () => {
199
+ let snap: any;
200
+
201
+ function ObjectTest() @{
202
+ const obj = new RippleObject({ a: 1, b: 2 });
203
+ obj.a = 10;
204
+ snap = snapshot(obj);
205
+ // a later mutation must not affect the snapshot
206
+ obj.a = 99;
207
+ <div />
208
+ }
209
+
210
+ render(ObjectTest);
211
+
212
+ expect(snap).toEqual({ a: 10, b: 2 });
213
+ // detached: a plain object, not a proxy
214
+ expect(TRACKED_OBJECT in snap).toBe(false);
215
+ expect(snap.a).toBe(10);
216
+ });
217
+
218
+ it('snapshot() reads without registering reactivity', () => {
219
+ let runs = 0;
220
+
221
+ function ObjectTest() @{
222
+ const obj = new RippleObject({ a: 0 });
223
+ effect(() => {
224
+ snapshot(obj);
225
+ runs++;
226
+ });
227
+ <button
228
+ onClick={() => {
229
+ obj.a++;
230
+ }}
231
+ >{'Increment A'}</button>
232
+ }
233
+
234
+ render(ObjectTest);
235
+ flushSync();
236
+ expect(runs).toBe(1);
237
+
238
+ container.querySelectorAll('button')[0].click();
239
+ flushSync();
240
+ // snapshot did not subscribe, so the effect must not re-run
241
+ expect(runs).toBe(1);
242
+ });
197
243
  });
package/types/index.d.ts CHANGED
@@ -69,6 +69,8 @@ export function tick(): Promise<void>;
69
69
 
70
70
  export function untrack<T>(fn: () => T): T;
71
71
 
72
+ export function snapshot<T extends Iterable<unknown> | object>(value: T): T;
73
+
72
74
  export function flushSync<T>(fn?: () => T): T;
73
75
 
74
76
  export function effect(fn: (() => void) | (() => () => void)): void;