plutonium 0.26.3 → 0.26.4

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.
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.26.3"
2
+ VERSION = "0.26.4"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radioactive-labs/plutonium",
3
- "version": "0.4.9",
3
+ "version": "0.4.10",
4
4
  "description": "Core assets for the Plutonium gem",
5
5
  "type": "module",
6
6
  "main": "src/js/core.js",
@@ -26,6 +26,8 @@ export default class extends Controller {
26
26
  //======= Lifecycle
27
27
 
28
28
  connect() {
29
+ if (this.uppy) return;
30
+
29
31
  // initialize
30
32
  this.uploadedFiles = []
31
33
 
@@ -38,10 +40,48 @@ export default class extends Controller {
38
40
  this.#buildTriggers()
39
41
  // init state
40
42
  this.#onAttachmentsChanged()
43
+
44
+ // Just recreate Uppy after morphing - preserve existing attachments
45
+ this.element.addEventListener("turbo:morph-element", (event) => {
46
+ if (event.target === this.element && !this.morphing) {
47
+ this.morphing = true;
48
+ requestAnimationFrame(() => {
49
+ this.#handleMorph();
50
+ this.morphing = false;
51
+ });
52
+ }
53
+ });
41
54
  }
42
55
 
43
56
  disconnect() {
44
- this.uppy = null
57
+ this.#cleanupUppy();
58
+ }
59
+
60
+ #handleMorph() {
61
+ if (!this.element.isConnected) return;
62
+
63
+ // Clean up the old instance
64
+ this.#cleanupUppy();
65
+
66
+ // Recreate everything - Uppy, triggers, etc.
67
+ this.uploadedFiles = []
68
+ this.element.style["display"] = "none"
69
+ this.configureUppy()
70
+ this.#buildTriggers()
71
+ this.#onAttachmentsChanged()
72
+ }
73
+
74
+ #cleanupUppy() {
75
+ if (this.uppy) {
76
+ this.uppy.destroy();
77
+ this.uppy = null;
78
+ }
79
+
80
+ // Clean up triggers
81
+ if (this.triggerContainer && this.triggerContainer.parentNode) {
82
+ this.triggerContainer.parentNode.removeChild(this.triggerContainer);
83
+ this.triggerContainer = null;
84
+ }
45
85
  }
46
86
 
47
87
  attachmentPreviewOutletConnected(outlet, element) {
@@ -4,21 +4,60 @@ import { marked } from 'marked';
4
4
 
5
5
  // Connects to data-controller="easymde"
6
6
  export default class extends Controller {
7
+ static targets = ["textarea"]
8
+
7
9
  connect() {
10
+ if (this.easyMDE) return
11
+
12
+ this.originalValue = this.element.value
8
13
  this.easyMDE = new EasyMDE(this.#buildOptions())
9
- this.element.setAttribute("data-action", "turbo:morph-element->easymde#reconnect")
14
+
15
+ // Store the editor content before morphing
16
+ this.element.addEventListener("turbo:before-morph-element", (event) => {
17
+ if (event.target === this.element && this.easyMDE) {
18
+ this.storedValue = this.easyMDE.value()
19
+ }
20
+ })
21
+
22
+ // Restore after morphing
23
+ this.element.addEventListener("turbo:morph-element", (event) => {
24
+ if (event.target === this.element) {
25
+ requestAnimationFrame(() => this.#handleMorph())
26
+ }
27
+ })
10
28
  }
11
29
 
12
30
  disconnect() {
13
31
  if (this.easyMDE) {
14
- this.easyMDE.toTextArea()
32
+ try {
33
+ // Only call toTextArea if the element is still in the DOM
34
+ if (this.element.isConnected && this.element.parentNode) {
35
+ this.easyMDE.toTextArea()
36
+ }
37
+ } catch (error) {
38
+ console.warn('EasyMDE cleanup error:', error)
39
+ }
15
40
  this.easyMDE = null
16
41
  }
17
42
  }
18
-
19
- reconnect() {
20
- this.disconnect()
21
- this.connect()
43
+
44
+ #handleMorph() {
45
+ if (!this.element.isConnected) return
46
+
47
+ // Don't call toTextArea during morph - just clean up references
48
+ if (this.easyMDE) {
49
+ // Skip toTextArea cleanup - it causes DOM errors during morphing
50
+ this.easyMDE = null
51
+ }
52
+
53
+ // Recreate the editor
54
+ this.easyMDE = new EasyMDE(this.#buildOptions())
55
+
56
+ // Restore the stored value if we have it
57
+ if (this.storedValue !== undefined) {
58
+ this.easyMDE.value(this.storedValue)
59
+ this.storedValue = undefined
60
+ }
22
61
  }
23
62
 
24
63
  #buildOptions() {
@@ -3,14 +3,21 @@ import { Controller } from "@hotwired/stimulus";
3
3
  // Connects to data-controller="flatpickr"
4
4
  export default class extends Controller {
5
5
  connect() {
6
- this.modal = document.querySelector("[data-controller=remote-modal]");
6
+ if (this.picker) return;
7
7
 
8
+ this.modal = document.querySelector("[data-controller=remote-modal]");
8
9
  this.picker = new flatpickr(this.element, this.#buildOptions());
9
10
 
10
- this.element.setAttribute(
11
- "data-action",
12
- "turbo:morph-element->flatpickr#reconnect"
13
- );
11
+ // Just recreate Flatpickr after morphing - the DOM will have correct value
12
+ this.element.addEventListener("turbo:morph-element", (event) => {
13
+ if (event.target === this.element && !this.morphing) {
14
+ this.morphing = true;
15
+ requestAnimationFrame(() => {
16
+ this.#handleMorph();
17
+ this.morphing = false;
18
+ });
19
+ }
20
+ });
14
21
  }
15
22
 
16
23
  disconnect() {
@@ -20,9 +27,18 @@ export default class extends Controller {
20
27
  }
21
28
  }
22
29
 
23
- reconnect() {
24
- this.disconnect();
25
- this.connect();
30
+ #handleMorph() {
31
+ if (!this.element.isConnected) return;
32
+
33
+ // Clean up the old instance
34
+ if (this.picker) {
35
+ this.picker.destroy();
36
+ this.picker = null;
37
+ }
38
+
39
+ // Recreate the picker - it will pick up the current DOM value
40
+ this.modal = document.querySelector("[data-controller=remote-modal]");
41
+ this.picker = new flatpickr(this.element, this.#buildOptions());
26
42
  }
27
43
 
28
44
  #buildOptions() {
@@ -12,10 +12,20 @@ export default class extends Controller {
12
12
  }
13
13
 
14
14
  inputTargetConnected() {
15
- if (!this.hasInputTarget) return;
15
+ if (!this.hasInputTarget || this.iti) return;
16
16
 
17
17
  this.iti = window.intlTelInput(this.inputTarget, this.#buildOptions())
18
- this.inputTarget.setAttribute("data-action", "turbo:morph-element->intl-tel-input#reconnect")
18
+
19
+ // Just recreate IntlTelInput after morphing - the DOM will have correct value
20
+ this.element.addEventListener("turbo:morph-element", (event) => {
21
+ if (event.target === this.element && !this.morphing) {
22
+ this.morphing = true;
23
+ requestAnimationFrame(() => {
24
+ this.#handleMorph();
25
+ this.morphing = false;
26
+ });
27
+ }
28
+ });
19
29
  }
20
30
 
21
31
  inputTargetDisconnected() {
@@ -25,9 +35,17 @@ export default class extends Controller {
25
35
  }
26
36
  }
27
37
 
28
- reconnect() {
29
- this.inputTargetDisconnected()
30
- this.inputTargetConnected()
38
+ #handleMorph() {
39
+ if (!this.inputTarget || !this.inputTarget.isConnected) return;
40
+
41
+ // Clean up the old instance
42
+ if (this.iti) {
43
+ this.iti.destroy();
44
+ this.iti = null;
45
+ }
46
+
47
+ // Recreate the intl tel input - it will pick up the current DOM value
48
+ this.iti = window.intlTelInput(this.inputTarget, this.#buildOptions());
31
49
  }
32
50
 
33
51
  #buildOptions() {
@@ -3,6 +3,19 @@ import { Controller } from "@hotwired/stimulus";
3
3
  // Connects to data-controller="slim-select"
4
4
  export default class extends Controller {
5
5
  connect() {
6
+ if (this.slimSelect) return;
7
+
8
+ this.#setupSlimSelect();
9
+
10
+ // Just recreate SlimSelect after morphing - the DOM will have correct selections
11
+ this.element.addEventListener("turbo:morph-element", (event) => {
12
+ if (event.target === this.element) {
13
+ requestAnimationFrame(() => this.#handleMorph());
14
+ }
15
+ });
16
+ }
17
+
18
+ #setupSlimSelect() {
6
19
  const settings = {};
7
20
  const modal = document.querySelector('[data-controller="remote-modal"]');
8
21
 
@@ -48,11 +61,6 @@ export default class extends Controller {
48
61
 
49
62
  // Add mutation observer to track aria-expanded attribute
50
63
  this.setupAriaObserver();
51
-
52
- this.element.setAttribute(
53
- "data-action",
54
- "turbo:morph-element->slim-select#reconnect"
55
- );
56
64
  }
57
65
 
58
66
  handleDropdownPosition() {
@@ -162,6 +170,20 @@ export default class extends Controller {
162
170
  }
163
171
 
164
172
  disconnect() {
173
+ this.#cleanupSlimSelect();
174
+ }
175
+
176
+ #handleMorph() {
177
+ if (!this.element.isConnected) return;
178
+
179
+ // Clean up the old instance without DOM manipulation
180
+ this.#cleanupSlimSelect();
181
+
182
+ // Recreate the select - it will automatically pick up the current DOM selections
183
+ this.#setupSlimSelect();
184
+ }
185
+
186
+ #cleanupSlimSelect() {
165
187
  // Clean up event listeners
166
188
  if (this.element) {
167
189
  if (this.boundHandleDropdownOpen) {
@@ -208,11 +230,4 @@ export default class extends Controller {
208
230
  this.modifiedSelectWrapper = null;
209
231
  }
210
232
  }
211
-
212
- reconnect() {
213
- this.disconnect();
214
- // dispatch this on the next frame.
215
- // there's some funny issue where my elements get removed from the DOM
216
- setTimeout(() => this.connect(), 10);
217
- }
218
233
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plutonium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.26.3
4
+ version: 0.26.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-07-26 00:00:00.000000000 Z
11
+ date: 2025-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk