ripple 0.4.1 → 0.4.3

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 (96) hide show
  1. package/CHANGELOG.md +162 -0
  2. package/package.json +9 -3
  3. package/src/constants.js +4 -0
  4. package/src/jsx-runtime.d.ts +2 -1
  5. package/src/runtime/array.js +15 -9
  6. package/src/runtime/index-client.js +25 -7
  7. package/src/runtime/index-server.js +1 -0
  8. package/src/runtime/internal/client/attributes.js +313 -0
  9. package/src/runtime/internal/client/blocks.js +4 -14
  10. package/src/runtime/internal/client/component.js +2 -5
  11. package/src/runtime/internal/client/composite.js +14 -6
  12. package/src/runtime/internal/client/constants.js +12 -6
  13. package/src/runtime/internal/client/context.js +3 -2
  14. package/src/runtime/internal/client/css.js +11 -26
  15. package/src/runtime/internal/client/errors.js +188 -0
  16. package/src/runtime/internal/client/events.js +79 -34
  17. package/src/runtime/internal/client/expression.js +21 -116
  18. package/src/runtime/internal/client/for.js +272 -222
  19. package/src/runtime/internal/client/head.js +4 -3
  20. package/src/runtime/internal/client/html.js +5 -3
  21. package/src/runtime/internal/client/hydrate.js +484 -0
  22. package/src/runtime/internal/client/hydration-enabled.js +7 -0
  23. package/src/runtime/internal/client/hydration.js +16 -97
  24. package/src/runtime/internal/client/if.js +13 -9
  25. package/src/runtime/internal/client/index.js +16 -8
  26. package/src/runtime/internal/client/operations.js +12 -9
  27. package/src/runtime/internal/client/portal.js +3 -2
  28. package/src/runtime/internal/client/render.js +75 -321
  29. package/src/runtime/internal/client/root-boundary-enabled.js +7 -0
  30. package/src/runtime/internal/client/runtime.js +129 -362
  31. package/src/runtime/internal/client/template-ns.js +47 -0
  32. package/src/runtime/internal/client/template.js +63 -140
  33. package/src/runtime/internal/client/track-async.js +356 -0
  34. package/src/runtime/internal/client/try.js +133 -319
  35. package/src/runtime/internal/client/types.d.ts +48 -24
  36. package/src/runtime/internal/server/index.js +38 -33
  37. package/src/runtime/internal/server/prerender.js +27 -0
  38. package/src/server/index.js +1 -0
  39. package/src/utils/class-name.js +28 -0
  40. package/tests/client/basic/basic.errors.test.tsrx +3 -3
  41. package/tests/client/compiler/__snapshots__/compiler.typescript.test.tsrx.snap +0 -2
  42. package/tests/client/compiler/compiler.basic.test.tsrx +4 -4
  43. package/tests/client/compiler/compiler.tracked-access.test.tsrx +3 -3
  44. package/tests/client/composite/composite.props.test.tsrx +10 -10
  45. package/tests/client/derived-release.test.tsrx +3 -3
  46. package/tests/client/for-item.test.tsrx +443 -0
  47. package/tests/client/for-selector.test.tsrx +95 -0
  48. package/tests/hydration/compiled/client/basic.js +97 -123
  49. package/tests/hydration/compiled/client/composite.js +5 -5
  50. package/tests/hydration/compiled/client/events.js +84 -79
  51. package/tests/hydration/compiled/client/for.js +229 -233
  52. package/tests/hydration/compiled/client/fragment-cursor.js +781 -481
  53. package/tests/hydration/compiled/client/head.js +23 -26
  54. package/tests/hydration/compiled/client/hmr.js +4 -4
  55. package/tests/hydration/compiled/client/html-in-template.js +5 -5
  56. package/tests/hydration/compiled/client/html.js +174 -208
  57. package/tests/hydration/compiled/client/if-children.js +71 -70
  58. package/tests/hydration/compiled/client/if-fragment-controlflow.js +46 -46
  59. package/tests/hydration/compiled/client/if.js +26 -26
  60. package/tests/hydration/compiled/client/mixed-control-flow.js +63 -105
  61. package/tests/hydration/compiled/client/nested-control-flow.js +170 -390
  62. package/tests/hydration/compiled/client/portal.js +10 -10
  63. package/tests/hydration/compiled/client/reactivity.js +36 -50
  64. package/tests/hydration/compiled/client/return.js +2 -2
  65. package/tests/hydration/compiled/client/streaming.js +34 -40
  66. package/tests/hydration/compiled/client/switch.js +25 -25
  67. package/tests/hydration/compiled/client/track-async-serialization.js +41 -41
  68. package/tests/hydration/compiled/client/try.js +15 -15
  69. package/tests/hydration/compiled/client/typed-text.js +8 -19
  70. package/tests/hydration/compiled/server/basic.js +6 -6
  71. package/tests/hydration/compiled/server/events.js +13 -61
  72. package/tests/hydration/compiled/server/for.js +23 -31
  73. package/tests/hydration/compiled/server/fragment-cursor.js +214 -366
  74. package/tests/hydration/compiled/server/head.js +6 -6
  75. package/tests/hydration/compiled/server/hmr.js +1 -1
  76. package/tests/hydration/compiled/server/html.js +1 -1
  77. package/tests/hydration/compiled/server/if-children.js +9 -9
  78. package/tests/hydration/compiled/server/if.js +5 -5
  79. package/tests/hydration/compiled/server/mixed-control-flow.js +4 -4
  80. package/tests/hydration/compiled/server/portal.js +1 -1
  81. package/tests/hydration/compiled/server/reactivity.js +9 -9
  82. package/tests/hydration/compiled/server/streaming.js +9 -9
  83. package/tests/hydration/compiled/server/switch.js +4 -4
  84. package/tests/hydration/compiled/server/track-async-serialization.js +16 -16
  85. package/tests/hydration/compiled/server/try.js +4 -4
  86. package/tests/hydration/compiled/server/typed-text.js +1 -1
  87. package/tests/hydration/components/fragment-cursor.tsrx +60 -0
  88. package/tests/hydration/components/track-async-serialization.tsrx +2 -2
  89. package/tests/hydration/fragment-cursor.test.js +34 -1
  90. package/tests/hydration/track-async-serialization.test.js +1 -1
  91. package/tests/server/basic.components.test.tsrx +3 -3
  92. package/tests/server/basic.test.tsrx +23 -0
  93. package/tests/server/track-async-serialization.test.tsrx +6 -6
  94. package/tests/utils/tracked-types.test.js +3 -3
  95. package/types/index.d.ts +8 -8
  96. package/types/server.d.ts +17 -0
@@ -48,6 +48,11 @@ export type AppendIntoAnchor = {
48
48
  parent: Node;
49
49
  /** Marks the sentinel; a missing-property read on a DOM node is cheaper than `in`. */
50
50
  into: true;
51
+ /**
52
+ * The sentinel is the tail of `parent`: nothing the framework renders
53
+ * follows what it anchors, so an `@if` there never needs an anchor node.
54
+ */
55
+ tail: boolean;
51
56
  };
52
57
 
53
58
  export type Block = {
@@ -77,30 +82,50 @@ export type TryPendingFunction = (anchor: Node) => void;
77
82
  * helpers are module functions that compile only when a boundary uses them.
78
83
  */
79
84
  export type TryState = {
80
- anchor: Node;
81
- try_fn: (anchor: Node, block?: Block) => void;
82
- catch_fn: TryCatchFunction | null;
83
- pending_fn: TryPendingFunction | null;
85
+ /** anchor */
86
+ a: Node;
87
+ /** try function */
88
+ fn: (anchor: Node, block?: Block) => void;
89
+ /** catch function */
90
+ c: TryCatchFunction | null;
91
+ /** pending function */
92
+ p: TryPendingFunction | null;
84
93
  /** the catch branch's `reset` callback, created on first use */
85
- reset: (() => void) | null;
86
- pending_count: number;
87
- request_version: number;
88
- active_requests: Set<number>;
89
- try_block: Block | null;
90
- resolved_branch: Block | null;
91
- pending_branch: Block | null;
92
- catch_branch: Block | null;
93
- offscreen_fragment: DocumentFragment | null;
94
- has_resolved: boolean;
95
- mode: 'resolved' | 'pending' | 'catch';
96
- pending_deferreds: Map<number, (reason: any) => void>;
97
- paused_blocks: Set<Block>;
94
+ r: (() => void) | null;
95
+ /** pending request count */
96
+ n: number;
97
+ /** request version */
98
+ v: number;
99
+ /** active request ids */
100
+ q: Set<number>;
101
+ /** the try block */
102
+ b: Block | null;
103
+ /** resolved branch */
104
+ rb: Block | null;
105
+ /** pending branch */
106
+ pb: Block | null;
107
+ /** catch branch */
108
+ cb: Block | null;
109
+ /** fragment holding the resolved branch while it is offscreen */
110
+ o: DocumentFragment | null;
111
+ /** has resolved */
112
+ h: boolean;
113
+ /** mode: 0 resolved, 1 pending, 2 catch */
114
+ m: 0 | 1 | 2;
115
+ /** deferred rejections by request id */
116
+ d: Map<number, (reason: any) => void>;
117
+ /** blocks paused on a pending read */
118
+ z: Set<Block>;
98
119
  /** a streamed slot this boundary hydrated, until its chunk activates it */
99
- streamed_id: string | null;
100
- streamed_errored: boolean;
101
- streamed_fallback: boolean;
102
- slot_open: Comment | null;
103
- slot_close: Comment | null;
120
+ si: string | null;
121
+ /** the streamed slot errored */
122
+ se: boolean;
123
+ /** the streamed slot carries a hydratable fallback */
124
+ sf: boolean;
125
+ /** streamed slot open marker */
126
+ so: Comment | null;
127
+ /** streamed slot close marker */
128
+ sc: Comment | null;
104
129
  };
105
130
 
106
131
  export type BlockWithTryBoundary = Omit<Block, 's'> & {
@@ -108,7 +133,7 @@ export type BlockWithTryBoundary = Omit<Block, 's'> & {
108
133
  };
109
134
 
110
135
  export type BlockWithTryBoundaryAndCatch = Omit<BlockWithTryBoundary, 's'> & {
111
- s: TryState & { catch_fn: TryCatchFunction };
136
+ s: TryState & { c: TryCatchFunction };
112
137
  };
113
138
 
114
139
  export type RootBoundaryOptions = {
@@ -144,7 +169,6 @@ declare global {
144
169
 
145
170
  interface Element {
146
171
  __attributes?: {
147
- checked?: boolean;
148
172
  value?: string;
149
173
  };
150
174
  __click?: () => void;
@@ -60,7 +60,7 @@ import {
60
60
  is_boolean_attribute,
61
61
  normalize_css_property_name,
62
62
  } from '@tsrx/core/runtime/html';
63
- import { clsx } from 'clsx';
63
+ import { class_name } from '../../../utils/class-name.js';
64
64
  import { create_ref_prop } from '@tsrx/core/runtime/ref';
65
65
  import {
66
66
  BLOCK_CLOSE,
@@ -1258,7 +1258,6 @@ export async function render(component, options = {}) {
1258
1258
  }
1259
1259
  }
1260
1260
 
1261
- var CONTENT_SPECIAL = /[&<]/;
1262
1261
  var ATTR_SPECIAL = /[&"<]/;
1263
1262
  var SCRIPT_SPECIAL = /[<>]/;
1264
1263
 
@@ -1275,15 +1274,19 @@ function escape_inline_script(str) {
1275
1274
 
1276
1275
  /**
1277
1276
  * Escapes text or attribute content. Strings without a character to escape
1278
- * (the common case) return as they are after a single regex test; anything
1279
- * else goes through the general escaper.
1277
+ * (the common case) return as they are; anything else goes through the general
1278
+ * escaper. Text content is checked with two single-character searches: on the
1279
+ * short strings a page is made of they beat one character-class regex test.
1280
1280
  * @param {unknown} value
1281
1281
  * @param {boolean} [is_attr]
1282
1282
  * @returns {string}
1283
1283
  */
1284
1284
  export function escape(value, is_attr) {
1285
1285
  var str = typeof value === 'string' ? value : value == null ? '' : String(value);
1286
- return (is_attr ? ATTR_SPECIAL : CONTENT_SPECIAL).test(str) ? escape_html(str, is_attr) : str;
1286
+ if (is_attr) {
1287
+ return ATTR_SPECIAL.test(str) ? escape_html(str, true) : str;
1288
+ }
1289
+ return str.includes('&') || str.includes('<') ? escape_html(str, false) : str;
1287
1290
  }
1288
1291
 
1289
1292
  /**
@@ -1596,7 +1599,7 @@ export function attr(name, value, is_boolean = false) {
1596
1599
  }
1597
1600
  if (value == null || (!value && is_boolean)) return '';
1598
1601
  const normalized = (name in replacements && replacements[name].get(value)) || value;
1599
- let value_to_escape = name === 'class' ? clsx(normalized) : normalized;
1602
+ let value_to_escape = name === 'class' ? class_name(normalized) : normalized;
1600
1603
  value_to_escape =
1601
1604
  name === 'style'
1602
1605
  ? typeof value !== 'string'
@@ -1716,15 +1719,6 @@ class TrackedValue {
1716
1719
  set value(v) {
1717
1720
  set(/** @type {Tracked} */ (this), v);
1718
1721
  }
1719
- /**
1720
- * A read-only view: a derived over this value, for a receiver that should
1721
- * read but not write it. Equivalent to `track(() => tracked.value)`. The
1722
- * view has no hash of its own: it is not a serialized value.
1723
- * @returns {Derived}
1724
- */
1725
- readOnly() {
1726
- return derived(() => get_tracked(/** @type {Tracked} */ (this)));
1727
- }
1728
1722
  }
1729
1723
 
1730
1724
  class DerivedValue {
@@ -1762,17 +1756,6 @@ class DerivedValue {
1762
1756
  set value(v) {
1763
1757
  set(/** @type {Derived} */ (this), v);
1764
1758
  }
1765
- /**
1766
- * A read-only view (see `TrackedValue#readOnly`). A derived without a
1767
- * setter is already read-only and is returned as is; a writable one is
1768
- * wrapped in a derived that follows it.
1769
- * @returns {Derived}
1770
- */
1771
- readOnly() {
1772
- return this.a.set === undefined
1773
- ? /** @type {Derived} */ (this)
1774
- : derived(() => get_derived(/** @type {Derived} */ (this)));
1775
- }
1776
1759
  }
1777
1760
 
1778
1761
  /**
@@ -1801,6 +1784,25 @@ function derived(v, hash, get, set) {
1801
1784
  );
1802
1785
  }
1803
1786
 
1787
+ /**
1788
+ * A read-only view of a tracked or derived value (see the client runtime's
1789
+ * `track_read_only`). The view has no hash of its own: it is not a serialized
1790
+ * value.
1791
+ * @param {any} value
1792
+ * @returns {any}
1793
+ */
1794
+ export function track_read_only(value) {
1795
+ if (!is_ripple_object(value)) {
1796
+ return value;
1797
+ }
1798
+ if ((value.f & DERIVED) !== 0) {
1799
+ var d = /** @type {Derived} */ (value);
1800
+ return d.a.set === undefined ? d : derived(() => get_derived(d));
1801
+ }
1802
+ var t = /** @type {Tracked} */ (value);
1803
+ return derived(() => get_tracked(t));
1804
+ }
1805
+
1804
1806
  /**
1805
1807
  * @param {any} v
1806
1808
  * @param {string} hash
@@ -2275,7 +2277,7 @@ function run_track_async(t, fn, block, dr, dj) {
2275
2277
  /**
2276
2278
  * The hashes of the compile-time tracked values a trackAsync read, recorded
2277
2279
  * in its hydration envelope so the client re-subscribes to them. A dependency
2278
- * without a hash that is itself a derived (a `readOnly()` view, a derived made
2280
+ * without a hash that is itself a derived (a `trackReadOnly()` view, a derived made
2279
2281
  * outside a compiled call) stands for what it read: its own dependencies are
2280
2282
  * recorded in its place.
2281
2283
  * @param {Dependency | null} head
@@ -2527,35 +2529,38 @@ export function ripple_array(...elements) {
2527
2529
  }
2528
2530
 
2529
2531
  /**
2532
+ * `RippleArray.from(...)`
2530
2533
  * @template T
2531
2534
  * @param {ArrayLike<T> | Iterable<T>} arrayLike
2532
2535
  * @param {(v: T, k: number) => any | undefined} [map_fn]
2533
2536
  * @param {any} [thisArg]
2534
2537
  * @returns {T[]}
2535
2538
  */
2536
- ripple_array.from = function (arrayLike, map_fn, thisArg) {
2539
+ export function ripple_array_from(arrayLike, map_fn, thisArg) {
2537
2540
  return map_fn ? Array.from(arrayLike, map_fn, thisArg) : Array.from(arrayLike);
2538
- };
2541
+ }
2539
2542
 
2540
2543
  /**
2544
+ * `RippleArray.of(...)`
2541
2545
  * @template T
2542
2546
  * @param {...T} items
2543
2547
  * @returns {T[]}
2544
2548
  */
2545
- ripple_array.of = function (...items) {
2549
+ export function ripple_array_of(...items) {
2546
2550
  return Array.of(...items);
2547
- };
2551
+ }
2548
2552
 
2549
2553
  /**
2554
+ * `RippleArray.fromAsync(...)`
2550
2555
  * @template T
2551
2556
  * @param {ArrayLike<T> | Iterable<T>} arrayLike
2552
2557
  * @param {(v: T, k: number) => any | undefined} [map_fn]
2553
2558
  * @param {any} [thisArg]
2554
2559
  * @returns {Promise<T[]>}
2555
2560
  */
2556
- ripple_array.from_async = async function (arrayLike, map_fn, thisArg) {
2561
+ export async function ripple_array_from_async(arrayLike, map_fn, thisArg) {
2557
2562
  return map_fn ? Array.fromAsync(arrayLike, map_fn, thisArg) : Array.fromAsync(arrayLike);
2558
- };
2563
+ }
2559
2564
 
2560
2565
  /**
2561
2566
  * @param {object} obj
@@ -0,0 +1,27 @@
1
+ /** @import { RenderComponent } from './index.js' */
2
+ /** @import { RenderResult } from 'ripple/server' */
3
+ /** @import { RootBoundaryOptions } from '#public' */
4
+
5
+ import { render } from './index.js';
6
+ import { get_css_text } from './css-registry.js';
7
+
8
+ /**
9
+ * Renders a component to static HTML: buffered, every `@try` boundary and
10
+ * `trackAsync` settled before it resolves, with the scoped CSS as text. The
11
+ * static-generation counterpart of `render()`, for HTML written at build time
12
+ * that `hydrate()` picks up like any server-rendered page.
13
+ * @param {RenderComponent} component
14
+ * @param {{ rootBoundary?: RootBoundaryOptions }} [options]
15
+ * @returns {Promise<{ head: string, body: string, css: string, topLevelError: Error | null }>}
16
+ */
17
+ export async function prerender(component, options = {}) {
18
+ const result = /** @type {RenderResult} */ (
19
+ await render(component, { rootBoundary: options.rootBoundary })
20
+ );
21
+ return {
22
+ head: result.head,
23
+ body: result.body,
24
+ css: get_css_text(result.css),
25
+ topLevelError: result.topLevelError ?? null,
26
+ };
27
+ }
@@ -1,6 +1,7 @@
1
1
  // SSR helpers
2
2
  export { create_ssr_stream as createStream, render } from '../runtime/internal/server/index.js';
3
3
  export { get_css_text as getCss } from '../runtime/internal/server/css-registry.js';
4
+ export { prerender } from '../runtime/internal/server/prerender.js';
4
5
  export { executeServerFunction } from '../runtime/internal/server/rpc.js';
5
6
 
6
7
  // Re-export server runtime for components compiled for SSR
@@ -0,0 +1,28 @@
1
+ /**
2
+ * clsx-style class composition, shared by the client and server renderers: a
3
+ * string as it is, a truthy number, the truthy items of an array, the keys of
4
+ * an object with truthy values, nested arrays flattened, everything else
5
+ * dropped.
6
+ * @param {any} value
7
+ * @returns {string}
8
+ */
9
+ export function class_name(value) {
10
+ if (typeof value === 'string') {
11
+ return value;
12
+ }
13
+ if (typeof value !== 'object' || value === null) {
14
+ return typeof value === 'number' && value ? '' + value : '';
15
+ }
16
+ var str = '';
17
+ if (Array.isArray(value)) {
18
+ for (var i = 0; i < value.length; i++) {
19
+ var item = value[i] && class_name(value[i]);
20
+ if (item) str = str ? str + ' ' + item : item;
21
+ }
22
+ } else {
23
+ for (var key in value) {
24
+ if (value[key]) str = str ? str + ' ' + key : key;
25
+ }
26
+ }
27
+ return str;
28
+ }
@@ -126,7 +126,7 @@ describe('basic client > errors', () => {
126
126
 
127
127
  render(Basic);
128
128
 
129
- expect(error).toBe(
129
+ expect(error).toContain(
130
130
  'Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation',
131
131
  );
132
132
  });
@@ -148,7 +148,7 @@ describe('basic client > errors', () => {
148
148
 
149
149
  render(Basic);
150
150
 
151
- expect(error).toBe(
151
+ expect(error).toContain(
152
152
  'Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation',
153
153
  );
154
154
  });
@@ -169,7 +169,7 @@ describe('basic client > errors', () => {
169
169
 
170
170
  render(Basic);
171
171
 
172
- expect(error).toBe(
172
+ expect(error).toContain(
173
173
  'Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation',
174
174
  );
175
175
  });
@@ -30,8 +30,6 @@ exports[`compiler > typescript > removes class extends type arguments from JS ou
30
30
 
31
31
  class StringMap extends Map {
32
32
  constructor() {
33
- var __block = _$_.scope();
34
-
35
33
  super();
36
34
  }
37
35
  }"
@@ -110,8 +110,8 @@ describe('compiler > basics', () => {
110
110
 
111
111
  const { code } = compile(source, 'stringish-text.tsrx', { mode: 'client' });
112
112
 
113
- expect(code).toContain('_$_.set_text');
114
- expect(code).toContain('nodeValue = title');
113
+ expect(code).toContain('_$_.set_text_content');
114
+ expect(code).toContain('textContent = title');
115
115
  expect(count_occurrences(code, '_$_.expression')).toBe(2);
116
116
  });
117
117
 
@@ -213,8 +213,8 @@ export function App() @{
213
213
 
214
214
  // Already a string: no String(… ?? '') double wrap around it, and the
215
215
  // scope wrapper sits on the user call, not on the coercion.
216
- expect(code).toContain('String(_$_.with_scope(__prev.___block, fetchLabel))');
217
- expect(code).not.toContain('_$_.with_scope(__prev.___block, () => String(');
216
+ expect(code).toMatch(/String\(_\$_\.with_scope\(__prev\._[a-z]+, fetchLabel\)\)/);
217
+ expect(code).not.toMatch(/_\$_\.with_scope\(__prev\._[a-z]+, \(\) => String\(/);
218
218
  expect(code).not.toContain('String(String(');
219
219
  expect(code).not.toContain('?? \'\'');
220
220
  });
@@ -5,7 +5,7 @@ const value_message = /Accessing a tracked object directly is not allowed, use `
5
5
  describe('Compiler: Tracked Object Direct Access Checks', () => {
6
6
  it('should error on direct access to __v of a tracked object', () => {
7
7
  const code = `
8
- import { track } from 'ripple';
8
+ import { track, trackReadOnly } from 'ripple';
9
9
  export default function App() @{
10
10
  let count = track(0);
11
11
  console.log(count.__v);
@@ -200,12 +200,12 @@ import { track } from 'ripple';
200
200
  expect(() => compile(code, 'test.tsrx')).not.toThrow();
201
201
  });
202
202
 
203
- it('should allow .readOnly() on a tracked object', () => {
203
+ it('should allow .trackReadOnly() on a tracked object', () => {
204
204
  const code = `
205
205
  import { track } from 'ripple';
206
206
  export default function App() @{
207
207
  let count = track(0);
208
- const view = count.readOnly();
208
+ const view = trackReadOnly(count);
209
209
  <>{view.value}</>
210
210
  }
211
211
  `;
@@ -1,8 +1,8 @@
1
1
  import type { Derived, Tracked, Props } from 'ripple';
2
- import { effect, flushSync, track } from 'ripple';
2
+ import { effect, flushSync, track, trackReadOnly } from 'ripple';
3
3
 
4
4
  describe('composite > props', () => {
5
- it('hands a child a read-only view through readOnly()', () => {
5
+ it('hands a child a read-only view through trackReadOnly()', () => {
6
6
  function Child({ count }: { count: Derived<number> }) @{
7
7
  <span class="child">{count.value}</span>
8
8
  }
@@ -10,7 +10,7 @@ describe('composite > props', () => {
10
10
  function App() @{
11
11
  const count = track(0);
12
12
  <>
13
- <Child count={count.readOnly()} />
13
+ <Child count={trackReadOnly(count)} />
14
14
  <button onClick={() => count.value++}>{'inc'}</button>
15
15
  </>
16
16
  }
@@ -24,13 +24,13 @@ describe('composite > props', () => {
24
24
  expect(container.querySelector('.child').textContent).toBe('1');
25
25
  });
26
26
 
27
- it('returns a read-only derived from readOnly() as is and wraps a writable one', () => {
27
+ it('returns a read-only derived from trackReadOnly() as is and wraps a writable one', () => {
28
28
  function App() @{
29
29
  const count = track(1);
30
30
  const double = track(() => count.value * 2);
31
31
  const draft = track(() => count.value, undefined, true);
32
- const double_view = double.readOnly();
33
- const draft_view = draft.readOnly();
32
+ const double_view = trackReadOnly(double);
33
+ const draft_view = trackReadOnly(draft);
34
34
  expect(double_view).toBe(double);
35
35
  expect(draft_view).not.toBe(draft);
36
36
  <>
@@ -51,7 +51,7 @@ describe('composite > props', () => {
51
51
  expect(container.querySelector('.view').textContent).toBe('42');
52
52
  });
53
53
 
54
- it('releases a readOnly() view with the block that created it', () => {
54
+ it('releases a trackReadOnly() view with the block that created it', () => {
55
55
  let set_rows: (rows: number[]) => void = () => {};
56
56
  let bump: () => void = () => {};
57
57
  let source: Tracked<number> | undefined;
@@ -73,7 +73,7 @@ describe('composite > props', () => {
73
73
  <ul>
74
74
  @for (const row of rows.value; key row) {
75
75
  <li>
76
- <Child count={count.readOnly()} />
76
+ <Child count={trackReadOnly(count)} />
77
77
  </li>
78
78
  }
79
79
  </ul>
@@ -106,12 +106,12 @@ describe('composite > props', () => {
106
106
  expect(subscribers()).toBe(0);
107
107
  });
108
108
 
109
- it('keeps a readOnly() view from writing through to its source', () => {
109
+ it('keeps a trackReadOnly() view from writing through to its source', () => {
110
110
  const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
111
111
  try {
112
112
  function App() @{
113
113
  const count = track(1);
114
- const view = count.readOnly();
114
+ const view = trackReadOnly(count);
115
115
  const write = () => {
116
116
  view.value = 99;
117
117
  };
@@ -1,5 +1,5 @@
1
1
  import type { Derived, Tracked } from 'ripple';
2
- import { effect, flushSync, track } from 'ripple';
2
+ import { effect, flushSync, track, trackReadOnly } from 'ripple';
3
3
 
4
4
  /** The runtime's own subscriber list of a tracked value (`sb` / `sn`). */
5
5
  function subscribers(source: Tracked<number>) {
@@ -34,7 +34,7 @@ describe('derived release', () => {
34
34
  const label = track(() => 'n=' + count.value);
35
35
  <>
36
36
  <span class="label">{label.value}</span>
37
- <Child count={count.readOnly()} />
37
+ <Child count={trackReadOnly(count)} />
38
38
  </>
39
39
  }
40
40
  </>
@@ -73,7 +73,7 @@ describe('derived release', () => {
73
73
  <ul>
74
74
  @for (const row of rows.value; key row) {
75
75
  <li>
76
- <Child count={count.readOnly()} />
76
+ <Child count={trackReadOnly(count)} />
77
77
  </li>
78
78
  }
79
79
  </ul>