ripple 0.3.57 → 0.3.58

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,22 @@
1
1
  # ripple
2
2
 
3
+ ## 0.3.58
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1130](https://github.com/Ripple-TS/ripple/pull/1130)
8
+ [`0a5f39b`](https://github.com/Ripple-TS/ripple/commit/0a5f39b6e13807dfd3dc1228f40d7bb02b933373)
9
+ Thanks [@leonidaz](https://github.com/leonidaz)! - Fix client cleanup for
10
+ HMR-wrapped roots that do not own their DOM range directly.
11
+
12
+ - Updated dependencies
13
+ [[`b54fdfc`](https://github.com/Ripple-TS/ripple/commit/b54fdfc3ebfea29ac613307b76732c5bf5f49ab5),
14
+ [`0a5f39b`](https://github.com/Ripple-TS/ripple/commit/0a5f39b6e13807dfd3dc1228f40d7bb02b933373),
15
+ [`165703c`](https://github.com/Ripple-TS/ripple/commit/165703c588b52f3dc0d26c06187f21700d448693)]:
16
+ - @tsrx/core@0.1.8
17
+ - ripple@0.3.58
18
+ - @tsrx/ripple@0.1.8
19
+
3
20
  ## 0.3.57
4
21
 
5
22
  ### 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.57",
6
+ "version": "0.3.58",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -76,8 +76,8 @@
76
76
  "esm-env": "^1.2.2",
77
77
  "@types/estree": "^1.0.8",
78
78
  "@types/estree-jsx": "^1.0.5",
79
- "@tsrx/core": "0.1.7",
80
- "@tsrx/ripple": "0.1.7"
79
+ "@tsrx/core": "0.1.8",
80
+ "@tsrx/ripple": "0.1.8"
81
81
  },
82
82
  "devDependencies": {
83
83
  "@types/node": "^24.3.0",
@@ -87,6 +87,6 @@
87
87
  "vscode-languageserver-types": "^3.17.5"
88
88
  },
89
89
  "peerDependencies": {
90
- "ripple": "0.3.57"
90
+ "ripple": "0.3.58"
91
91
  }
92
92
  }
@@ -188,7 +188,7 @@ export function root(fn, compat) {
188
188
  };
189
189
  }
190
190
 
191
- return block(ROOT_BLOCK, target_fn, { compat }, create_component_ctx());
191
+ return block(ROOT_BLOCK, target_fn, { compat, start: null, end: null }, create_component_ctx());
192
192
  }
193
193
 
194
194
  /**
@@ -274,7 +274,7 @@ export function destroy_block_children(parent, remove_dom = false) {
274
274
  var block = parent.first;
275
275
  parent.first = parent.last = null;
276
276
 
277
- if ((parent.f & CONTAINS_TEARDOWN) !== 0) {
277
+ if (remove_dom || (parent.f & CONTAINS_TEARDOWN) !== 0) {
278
278
  while (block !== null) {
279
279
  var next = block.next;
280
280
  destroy_block(block, remove_dom);
@@ -456,7 +456,7 @@ export function destroy_block(block, remove_dom = true) {
456
456
  (f & HEAD_BLOCK) !== 0
457
457
  ) {
458
458
  var s = block.s;
459
- if (s !== null) {
459
+ if (s !== null && s.start !== null) {
460
460
  remove_block_dom(s.start, s.end);
461
461
  removed = true;
462
462
  }
@@ -1,5 +1,6 @@
1
1
  import { HMR } from '../../../src/runtime/internal/client/constants.js';
2
2
  import { hmr } from '../../../src/runtime/internal/client/hmr.js';
3
+ import { effect, flushSync, mount } from 'ripple';
3
4
 
4
5
  describe('basic client > hmr', () => {
5
6
  it('handles updates before first render', () => {
@@ -16,4 +17,81 @@ describe('basic client > hmr', () => {
16
17
  expect(wrapper[HMR].current).toBeUndefined();
17
18
  expect(wrapper[HMR].fn).toBe(updated_component);
18
19
  });
20
+
21
+ it('removes DOM when an HMR-wrapped root is unmounted', () => {
22
+ component App() {
23
+ <div class="hmr-root">{'hmr root'}</div>
24
+ }
25
+
26
+ const cleanup = mount(hmr(App), { target: container });
27
+ flushSync();
28
+
29
+ expect(container.querySelector('.hmr-root')?.textContent).toBe('hmr root');
30
+
31
+ cleanup();
32
+
33
+ expect(container.querySelector('.hmr-root')).toBeNull();
34
+ expect(container.innerHTML).toBe('');
35
+ });
36
+
37
+ it('runs child teardowns when an HMR-wrapped root is unmounted', () => {
38
+ let teardown_count = 0;
39
+
40
+ component Child() {
41
+ effect(() => {
42
+ return () => {
43
+ teardown_count++;
44
+ };
45
+ });
46
+
47
+ <span class="hmr-child">{'child'}</span>
48
+ }
49
+
50
+ component App() {
51
+ <Child />
52
+ }
53
+
54
+ const cleanup = mount(hmr(App), { target: container });
55
+ flushSync();
56
+
57
+ expect(container.querySelector('.hmr-child')?.textContent).toBe('child');
58
+ expect(teardown_count).toBe(0);
59
+
60
+ cleanup();
61
+
62
+ expect(container.querySelector('.hmr-child')).toBeNull();
63
+ expect(teardown_count).toBe(1);
64
+ });
65
+
66
+ it('does not double-remove child DOM when the root owns the DOM range', () => {
67
+ let remove_count = 0;
68
+ const original_remove = Element.prototype.remove;
69
+
70
+ Element.prototype.remove = function () {
71
+ if (this.classList.contains('owned-root')) {
72
+ remove_count++;
73
+ }
74
+ return original_remove.call(this);
75
+ };
76
+
77
+ try {
78
+ component App() {
79
+ <div class="owned-root">
80
+ <span>{'child'}</span>
81
+ </div>
82
+ }
83
+
84
+ const cleanup = mount(App, { target: container });
85
+ flushSync();
86
+
87
+ expect(container.querySelector('.owned-root')).not.toBeNull();
88
+
89
+ cleanup();
90
+
91
+ expect(container.querySelector('.owned-root')).toBeNull();
92
+ expect(remove_count).toBe(1);
93
+ } finally {
94
+ Element.prototype.remove = original_remove;
95
+ }
96
+ });
19
97
  });