plutonium 0.25.2 → 0.26.1

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,25 +1,218 @@
1
- import { Controller } from "@hotwired/stimulus"
1
+ import { Controller } from "@hotwired/stimulus";
2
2
 
3
3
  // Connects to data-controller="slim-select"
4
4
  export default class extends Controller {
5
5
  connect() {
6
+ const settings = {};
7
+ const modal = document.querySelector('[data-controller="remote-modal"]');
8
+
9
+ if (modal) {
10
+ // Create a dedicated container div right after the select element
11
+ this.dropdownContainer = document.createElement("div");
12
+ this.dropdownContainer.className = "ss-dropdown-container";
13
+
14
+ // Make the select wrapper position relative to contain the absolute dropdown
15
+ const selectWrapper = this.element.parentNode;
16
+ const originalPosition = getComputedStyle(selectWrapper).position;
17
+ if (originalPosition === "static") {
18
+ selectWrapper.style.position = "relative";
19
+ this.modifiedSelectWrapper = selectWrapper;
20
+ }
21
+
22
+ // Insert the container right after the select element
23
+ this.element.parentNode.insertBefore(
24
+ this.dropdownContainer,
25
+ this.element.nextSibling
26
+ );
27
+
28
+ settings.contentLocation = this.dropdownContainer;
29
+ settings.contentPosition = "absolute";
30
+ settings.openPosition = "auto";
31
+ }
32
+
6
33
  this.slimSelect = new SlimSelect({
7
- select: this.element
8
- })
9
- this.element.setAttribute("data-action", "turbo:morph-element->slim-select#reconnect")
34
+ select: this.element,
35
+ settings: settings,
36
+ });
37
+
38
+ // Add event listeners for better positioning
39
+ this.handleDropdownPosition();
40
+
41
+ // Bind event handlers for proper cleanup
42
+ this.boundHandleDropdownOpen = this.handleDropdownOpen.bind(this);
43
+ this.boundHandleDropdownClose = this.handleDropdownClose.bind(this);
44
+
45
+ // Add event listeners to properly handle dropdown visibility
46
+ this.element.addEventListener("ss:open", this.boundHandleDropdownOpen);
47
+ this.element.addEventListener("ss:close", this.boundHandleDropdownClose);
48
+
49
+ // Add mutation observer to track aria-expanded attribute
50
+ this.setupAriaObserver();
51
+
52
+ this.element.setAttribute(
53
+ "data-action",
54
+ "turbo:morph-element->slim-select#reconnect"
55
+ );
56
+ }
57
+
58
+ handleDropdownPosition() {
59
+ if (this.dropdownContainer) {
60
+ // Reposition dropdown when window resizes or scrolls
61
+ const repositionDropdown = () => {
62
+ const selectRect = this.element.getBoundingClientRect();
63
+
64
+ // Calculate if there's enough space below
65
+ const spaceBelow = window.innerHeight - selectRect.bottom;
66
+ const spaceAbove = selectRect.top;
67
+
68
+ if (spaceBelow < 200 && spaceAbove > spaceBelow) {
69
+ // Position above if not enough space below
70
+ this.dropdownContainer.style.top = "auto";
71
+ this.dropdownContainer.style.bottom = "100%";
72
+ this.dropdownContainer.style.borderRadius = "0.375rem 0.375rem 0 0";
73
+ } else {
74
+ // Position below (default)
75
+ this.dropdownContainer.style.bottom = "auto";
76
+ this.dropdownContainer.style.borderRadius = "0 0 0.375rem 0.375rem";
77
+ }
78
+ };
79
+
80
+ // Initial positioning
81
+ setTimeout(repositionDropdown, 0);
82
+
83
+ // Reposition on events
84
+ window.addEventListener("resize", repositionDropdown);
85
+ window.addEventListener("scroll", repositionDropdown);
86
+
87
+ // Store references for cleanup
88
+ this.repositionDropdown = repositionDropdown;
89
+ }
90
+ }
91
+
92
+ handleDropdownOpen() {
93
+ if (this.dropdownContainer) {
94
+ // When dropdown opens, ensure our container is properly sized
95
+ this.dropdownContainer.style.height = "auto";
96
+ this.dropdownContainer.style.overflow = "visible";
97
+
98
+ // Add open class for better CSS targeting
99
+ this.dropdownContainer.classList.add("ss-active");
100
+
101
+ // Ensure this dropdown appears above others
102
+ const allContainers = document.querySelectorAll(".ss-dropdown-container");
103
+ allContainers.forEach((container) => {
104
+ if (container !== this.dropdownContainer) {
105
+ container.style.zIndex = "9999";
106
+ }
107
+ });
108
+ this.dropdownContainer.style.zIndex = "10000";
109
+ }
110
+ }
111
+
112
+ handleDropdownClose() {
113
+ if (this.dropdownContainer) {
114
+ // Remove active class
115
+ this.dropdownContainer.classList.remove("ss-active");
116
+ }
117
+ }
118
+
119
+ setupAriaObserver() {
120
+ // Track aria-expanded attribute on the select element or its wrapper
121
+ if (this.element) {
122
+ this.ariaObserver = new MutationObserver((mutations) => {
123
+ mutations.forEach((mutation) => {
124
+ if (mutation.attributeName === "aria-expanded") {
125
+ const expanded =
126
+ mutation.target.getAttribute("aria-expanded") === "true";
127
+ if (expanded) {
128
+ this.handleDropdownOpen();
129
+ } else {
130
+ this.handleDropdownClose();
131
+ }
132
+ }
133
+ });
134
+ });
135
+
136
+ // Look for the actual element that gets the aria-expanded attribute
137
+ const possibleTargets = [
138
+ this.element,
139
+ this.element.parentNode.querySelector(".ss-main"),
140
+ this.element.parentNode.querySelector("[aria-expanded]"),
141
+ ];
142
+
143
+ const target = possibleTargets.find(
144
+ (el) => el && el.hasAttribute && el.hasAttribute("aria-expanded")
145
+ );
146
+
147
+ if (target) {
148
+ this.ariaObserver.observe(target, {
149
+ attributes: true,
150
+ attributeFilter: ["aria-expanded"],
151
+ });
152
+
153
+ // Check initial state
154
+ const expanded = target.getAttribute("aria-expanded") === "true";
155
+ if (expanded) {
156
+ this.handleDropdownOpen();
157
+ } else {
158
+ this.handleDropdownClose();
159
+ }
160
+ }
161
+ }
10
162
  }
11
163
 
12
164
  disconnect() {
165
+ // Clean up event listeners
166
+ if (this.element) {
167
+ if (this.boundHandleDropdownOpen) {
168
+ this.element.removeEventListener(
169
+ "ss:open",
170
+ this.boundHandleDropdownOpen
171
+ );
172
+ }
173
+ if (this.boundHandleDropdownClose) {
174
+ this.element.removeEventListener(
175
+ "ss:close",
176
+ this.boundHandleDropdownClose
177
+ );
178
+ }
179
+ }
180
+
181
+ // Disconnect observer
182
+ if (this.ariaObserver) {
183
+ this.ariaObserver.disconnect();
184
+ this.ariaObserver = null;
185
+ }
186
+
13
187
  if (this.slimSelect) {
14
- this.slimSelect.destroy()
15
- this.slimSelect = null
188
+ this.slimSelect.destroy();
189
+ this.slimSelect = null;
190
+ }
191
+
192
+ // Clean up event listeners
193
+ if (this.repositionDropdown) {
194
+ window.removeEventListener("resize", this.repositionDropdown);
195
+ window.removeEventListener("scroll", this.repositionDropdown);
196
+ this.repositionDropdown = null;
197
+ }
198
+
199
+ // Clean up the dropdown container if it exists
200
+ if (this.dropdownContainer && this.dropdownContainer.parentNode) {
201
+ this.dropdownContainer.parentNode.removeChild(this.dropdownContainer);
202
+ this.dropdownContainer = null;
203
+ }
204
+
205
+ // Restore original positioning if we modified it
206
+ if (this.modifiedSelectWrapper) {
207
+ this.modifiedSelectWrapper.style.position = "";
208
+ this.modifiedSelectWrapper = null;
16
209
  }
17
210
  }
18
211
 
19
212
  reconnect() {
20
- this.disconnect()
213
+ this.disconnect();
21
214
  // dispatch this on the next frame.
22
215
  // there's some funny issue where my elements get removed from the DOM
23
- setTimeout(() => this.connect(), 10)
216
+ setTimeout(() => this.connect(), 10);
24
217
  }
25
218
  }
data/src/js/core.js CHANGED
@@ -1,3 +1,4 @@
1
1
  import registerControllers from "./controllers/register_controllers.js"
2
-
3
2
  export { registerControllers }
3
+
4
+ import "./turbo"
data/src/js/plutonium.js CHANGED
@@ -5,5 +5,3 @@ const application = Application.start()
5
5
 
6
6
  import { registerControllers } from "./core"
7
7
  registerControllers(application)
8
-
9
- import "./turbo"