ripple 0.3.121 → 0.3.123

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,29 @@
1
1
  # ripple
2
2
 
3
+ ## 0.3.123
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1432](https://github.com/Ripple-TS/ripple/pull/1432)
8
+ [`8b5abd7`](https://github.com/Ripple-TS/ripple/commit/8b5abd7021a23b7651608fb26ff7e59ed5b4a18c)
9
+ Thanks [@chenzylab](https://github.com/chenzylab)! - Fix insertion order when a
10
+ keyed or ref-based `@for` inserts multiple new items in the middle of the list.
11
+ The pure-insert reconciliation path resolved the DOM anchor per new item by
12
+ indexing the old blocks with a new-list index, so the anchor drifted into the
13
+ matched suffix and later items landed after it (e.g. `[A, C, D]` →
14
+ `[A, B1, B2, C, D]` rendered as `A, B1, C, B2, D`). The anchor is now resolved
15
+ once, at the start of the matched suffix, and reused for the whole run of
16
+ inserts.
17
+
18
+ ## 0.3.122
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies
23
+ [[`481d934`](https://github.com/Ripple-TS/ripple/commit/481d934aa17a275aa588d945b4c65b421076f89c)]:
24
+ - @tsrx/core@0.1.60
25
+ - @tsrx/ripple@0.1.61
26
+
3
27
  ## 0.3.121
4
28
 
5
29
  ### 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.121",
6
+ "version": "0.3.123",
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.59",
77
- "@tsrx/ripple": "0.1.60"
76
+ "@tsrx/core": "0.1.60",
77
+ "@tsrx/ripple": "0.1.61"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@types/estree": "^1.0.8",
@@ -479,10 +479,10 @@ function reconcile_by_key(
479
479
 
480
480
  if (j > a_end) {
481
481
  if (j <= b_end) {
482
+ var insert_target = block_start(a_blocks, a_end + 1, a_length, anchor);
482
483
  while (j <= b_end) {
483
484
  b_val = b[j];
484
- var target = block_start(a_blocks, j, a_length, anchor);
485
- b_blocks[j] = create_item(target, b_val, j, render_fn, is_indexed, true);
485
+ b_blocks[j] = create_item(insert_target, b_val, j, render_fn, is_indexed, true);
486
486
  j++;
487
487
  }
488
488
  }
@@ -766,10 +766,10 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
766
766
 
767
767
  if (j > a_end) {
768
768
  if (j <= b_end) {
769
+ var insert_target = block_start(a_blocks, a_end + 1, a_length, anchor);
769
770
  while (j <= b_end) {
770
771
  b_val = b[j];
771
- var target = block_start(a_blocks, j, a_length, anchor);
772
- b_blocks[j] = create_item(target, b_val, j, render_fn, is_indexed, false);
772
+ b_blocks[j] = create_item(insert_target, b_val, j, render_fn, is_indexed, false);
773
773
  j++;
774
774
  }
775
775
  }
@@ -234,6 +234,78 @@ describe('for statements', () => {
234
234
  expect(container.querySelectorAll('.item').length).toBe(2);
235
235
  });
236
236
 
237
+ it('keyed for inserts multiple new items in the middle in correct order', () => {
238
+ function App() @{
239
+ let &[items] = track([
240
+ { id: 'a', text: 'A' },
241
+ { id: 'c', text: 'C' },
242
+ { id: 'd', text: 'D' },
243
+ ]);
244
+ <>
245
+ @for (const item of items; key item.id) {
246
+ <div class="item">{item.text}</div>
247
+ }
248
+ <button
249
+ onClick={() => {
250
+ items = [
251
+ { id: 'a', text: 'A' },
252
+ { id: 'b1', text: 'B1' },
253
+ { id: 'b2', text: 'B2' },
254
+ { id: 'c', text: 'C' },
255
+ { id: 'd', text: 'D' },
256
+ ];
257
+ }}
258
+ >{'Insert'}</button>
259
+ </>
260
+ }
261
+
262
+ render(App);
263
+
264
+ const getTexts = () => Array.from(container.querySelectorAll('.item')).map(
265
+ (el) => el.textContent,
266
+ );
267
+
268
+ expect(getTexts()).toEqual(['A', 'C', 'D']);
269
+
270
+ container.querySelector('button').click();
271
+ flushSync();
272
+
273
+ expect(getTexts()).toEqual(['A', 'B1', 'B2', 'C', 'D']);
274
+ });
275
+
276
+ it('ref-based for inserts multiple new items in the middle in correct order', () => {
277
+ const a = { text: 'A' };
278
+ const c = { text: 'C' };
279
+ const d = { text: 'D' };
280
+
281
+ function App() @{
282
+ let &[items] = track([a, c, d]);
283
+ <>
284
+ @for (const item of items) {
285
+ <div class="item">{item.text}</div>
286
+ }
287
+ <button
288
+ onClick={() => {
289
+ items = [a, { text: 'B1' }, { text: 'B2' }, c, d];
290
+ }}
291
+ >{'Insert'}</button>
292
+ </>
293
+ }
294
+
295
+ render(App);
296
+
297
+ const getTexts = () => Array.from(container.querySelectorAll('.item')).map(
298
+ (el) => el.textContent,
299
+ );
300
+
301
+ expect(getTexts()).toEqual(['A', 'C', 'D']);
302
+
303
+ container.querySelector('button').click();
304
+ flushSync();
305
+
306
+ expect(getTexts()).toEqual(['A', 'B1', 'B2', 'C', 'D']);
307
+ });
308
+
237
309
  it('keyed for with 32+ items: full reversal updates values via Map path', () => {
238
310
  function App() @{
239
311
  let &[items] = track(Array.from({ length: 40 }, (_, i) => ({ id: i, text: `Item ${i}` })));