plutonium 0.25.1 → 0.26.0
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.
- checksums.yaml +4 -4
- data/app/assets/plutonium.css +2 -2
- data/app/assets/plutonium.js +5577 -95
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +76 -45
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/javascript/controllers/key_value_store_controller.js +119 -0
- data/docs/guide/deep-dive/resources.md +1 -1
- data/docs/modules/definition.md +55 -20
- data/docs/modules/table.md +1 -1
- data/docs/public/plutonium.mdc +27 -14
- data/lib/plutonium/core/controller.rb +2 -2
- data/lib/plutonium/core/controllers/entity_scoping.rb +5 -0
- data/lib/plutonium/engine.rb +3 -2
- data/lib/plutonium/interaction/response/redirect.rb +12 -1
- data/lib/plutonium/resource/register.rb +1 -1
- data/lib/plutonium/ui/form/base.rb +4 -0
- data/lib/plutonium/ui/form/components/key_value_store.rb +234 -0
- data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +8 -2
- data/lib/plutonium/ui/page/interactive_action.rb +23 -0
- data/lib/plutonium/ui/table/resource.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/package.json +1 -1
- data/src/css/slim_select.css +37 -0
- data/src/js/controllers/key_value_store_controller.js +109 -0
- data/src/js/controllers/register_controllers.js +2 -0
- data/src/js/controllers/remote_modal_controller.js +24 -17
- data/src/js/controllers/slim_select_controller.js +201 -8
- data/src/js/core.js +2 -1
- data/src/js/plutonium.js +2 -2
- data/yarn.lock +3840 -0
- metadata +6 -2
@@ -91,12 +91,18 @@ module Plutonium
|
|
91
91
|
# @param [Symbol] name The name of the nested resource field
|
92
92
|
# @raise [ArgumentError] if the nested input definition is missing required configuration
|
93
93
|
def render_nested_resource_field(name)
|
94
|
+
nested_input_definition = resource_definition.defined_nested_inputs[name]
|
95
|
+
condition = nested_input_definition[:options]&.fetch(:condition, nil)
|
96
|
+
if condition && !instance_exec(&condition)
|
97
|
+
return
|
98
|
+
end
|
99
|
+
|
94
100
|
context = NestedFieldContext.new(
|
95
101
|
name: name,
|
96
102
|
definition: build_nested_fields_definition(name),
|
97
103
|
resource_class: resource_class,
|
98
104
|
resource_definition: resource_definition,
|
99
|
-
object_class:
|
105
|
+
object_class: nested_input_definition[:options]&.fetch(:object_class, nil)
|
100
106
|
)
|
101
107
|
|
102
108
|
render_nested_field_container(context) do
|
@@ -171,7 +177,7 @@ module Plutonium
|
|
171
177
|
end
|
172
178
|
|
173
179
|
def render_template_for_nested_fields(context, options, nesting_method:)
|
174
|
-
|
180
|
+
template data_nested_resource_form_fields_target: "template" do
|
175
181
|
send(nesting_method, context.name, as: context.nested_fields_input_param, **options, template: true) do |nested|
|
176
182
|
render_nested_fields_fieldset(nested, context)
|
177
183
|
end
|
@@ -32,6 +32,29 @@ module Plutonium
|
|
32
32
|
transition-opacity duration-300 ease-in-out",
|
33
33
|
data: {controller: "remote-modal"}
|
34
34
|
) do
|
35
|
+
# Close button
|
36
|
+
button(
|
37
|
+
type: "button",
|
38
|
+
class: "absolute top-4 right-4 p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors duration-200",
|
39
|
+
data: {action: "remote-modal#close"},
|
40
|
+
"aria-label": "Close dialog"
|
41
|
+
) do
|
42
|
+
svg(
|
43
|
+
class: "w-5 h-5",
|
44
|
+
fill: "none",
|
45
|
+
stroke: "currentColor",
|
46
|
+
viewBox: "0 0 24 24",
|
47
|
+
xmlns: "http://www.w3.org/2000/svg"
|
48
|
+
) do |s|
|
49
|
+
s.path(
|
50
|
+
stroke_linecap: "round",
|
51
|
+
stroke_linejoin: "round",
|
52
|
+
stroke_width: "2",
|
53
|
+
d: "M6 18L18 6M6 6l12 12"
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
35
58
|
render_page_header
|
36
59
|
render partial("interactive_action_form")
|
37
60
|
end
|
@@ -60,7 +60,7 @@ module Plutonium
|
|
60
60
|
column_options = column_definition[:options] || {}
|
61
61
|
|
62
62
|
# Check for conditional rendering
|
63
|
-
condition = column_options[:condition]
|
63
|
+
condition = column_options[:condition]
|
64
64
|
conditionally_hidden = condition && !instance_exec(&condition)
|
65
65
|
next if conditionally_hidden
|
66
66
|
|
data/lib/plutonium/version.rb
CHANGED
data/package.json
CHANGED
data/src/css/slim_select.css
CHANGED
@@ -309,3 +309,40 @@
|
|
309
309
|
.ss-main.ss-valid .ss-values .ss-placeholder {
|
310
310
|
@apply text-green-700 dark:text-green-500;
|
311
311
|
}
|
312
|
+
|
313
|
+
/* Modal-specific styles for SlimSelect dropdown */
|
314
|
+
.ss-dropdown-container {
|
315
|
+
position: absolute;
|
316
|
+
z-index: 9999;
|
317
|
+
inset: 40% 0px auto;
|
318
|
+
}
|
319
|
+
|
320
|
+
.ss-dropdown-container .ss-content {
|
321
|
+
position: static !important;
|
322
|
+
transform: none !important;
|
323
|
+
width: 100% !important;
|
324
|
+
border-radius: 0 !important;
|
325
|
+
margin: 0 !important;
|
326
|
+
pointer-events: none !important; /* Disabled by default */
|
327
|
+
}
|
328
|
+
|
329
|
+
/* When active (dropdown is expanded), enable pointer events */
|
330
|
+
.ss-dropdown-container.ss-active .ss-content {
|
331
|
+
pointer-events: auto !important;
|
332
|
+
}
|
333
|
+
|
334
|
+
.ss-dropdown-container .ss-list {
|
335
|
+
max-height: 250px !important;
|
336
|
+
overflow-y: auto !important;
|
337
|
+
}
|
338
|
+
|
339
|
+
/* Ensure the dropdown doesn't block other elements when closed */
|
340
|
+
.ss-dropdown-container:not(:has(.ss-content)),
|
341
|
+
.ss-dropdown-container:not(.ss-active) {
|
342
|
+
pointer-events: none !important;
|
343
|
+
}
|
344
|
+
|
345
|
+
/* Prevent interaction with closed dropdown */
|
346
|
+
.ss-dropdown-container:not(.ss-active) * {
|
347
|
+
pointer-events: none !important;
|
348
|
+
}
|
@@ -0,0 +1,109 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
// Connects to data-controller="key-value-store"
|
4
|
+
export default class extends Controller {
|
5
|
+
static targets = ["container", "pair", "template", "addButton", "keyInput", "valueInput"]
|
6
|
+
static values = { limit: Number }
|
7
|
+
|
8
|
+
connect() {
|
9
|
+
this.updateIndices()
|
10
|
+
this.updateAddButtonState()
|
11
|
+
}
|
12
|
+
|
13
|
+
addPair(event) {
|
14
|
+
event.preventDefault()
|
15
|
+
|
16
|
+
if (this.pairTargets.length >= this.limitValue) {
|
17
|
+
return
|
18
|
+
}
|
19
|
+
|
20
|
+
const template = this.templateTarget
|
21
|
+
const newPair = template.content.cloneNode(true)
|
22
|
+
const index = this.pairTargets.length
|
23
|
+
|
24
|
+
// Update the template placeholders with actual indices
|
25
|
+
this.updatePairIndices(newPair, index)
|
26
|
+
|
27
|
+
this.containerTarget.appendChild(newPair)
|
28
|
+
this.updateAddButtonState()
|
29
|
+
|
30
|
+
// Focus on the key input of the new pair
|
31
|
+
const newKeyInput = this.containerTarget.lastElementChild.querySelector('[data-key-value-store-target="keyInput"]')
|
32
|
+
if (newKeyInput) {
|
33
|
+
newKeyInput.focus()
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
removePair(event) {
|
38
|
+
event.preventDefault()
|
39
|
+
|
40
|
+
const pair = event.target.closest('[data-key-value-store-target="pair"]')
|
41
|
+
if (pair) {
|
42
|
+
pair.remove()
|
43
|
+
this.updateIndices()
|
44
|
+
this.updateAddButtonState()
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
updateIndices() {
|
49
|
+
this.pairTargets.forEach((pair, index) => {
|
50
|
+
const keyInput = pair.querySelector('[data-key-value-store-target="keyInput"]')
|
51
|
+
const valueInput = pair.querySelector('[data-key-value-store-target="valueInput"]')
|
52
|
+
|
53
|
+
if (keyInput) {
|
54
|
+
keyInput.name = keyInput.name.replace(/\[\d+\]/, `[${index}]`)
|
55
|
+
}
|
56
|
+
if (valueInput) {
|
57
|
+
valueInput.name = valueInput.name.replace(/\[\d+\]/, `[${index}]`)
|
58
|
+
}
|
59
|
+
})
|
60
|
+
}
|
61
|
+
|
62
|
+
updatePairIndices(element, index) {
|
63
|
+
const inputs = element.querySelectorAll('input')
|
64
|
+
inputs.forEach(input => {
|
65
|
+
if (input.name) {
|
66
|
+
input.name = input.name.replace('__INDEX__', index)
|
67
|
+
}
|
68
|
+
})
|
69
|
+
}
|
70
|
+
|
71
|
+
updateAddButtonState() {
|
72
|
+
const addButton = this.addButtonTarget
|
73
|
+
if (this.pairTargets.length >= this.limitValue) {
|
74
|
+
addButton.disabled = true
|
75
|
+
addButton.classList.add('opacity-50', 'cursor-not-allowed')
|
76
|
+
} else {
|
77
|
+
addButton.disabled = false
|
78
|
+
addButton.classList.remove('opacity-50', 'cursor-not-allowed')
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
// Serialize the current key-value pairs to JSON
|
83
|
+
toJSON() {
|
84
|
+
const pairs = {}
|
85
|
+
this.pairTargets.forEach(pair => {
|
86
|
+
const keyInput = pair.querySelector('[data-key-value-store-target="keyInput"]')
|
87
|
+
const valueInput = pair.querySelector('[data-key-value-store-target="valueInput"]')
|
88
|
+
|
89
|
+
if (keyInput && valueInput && keyInput.value.trim()) {
|
90
|
+
pairs[keyInput.value.trim()] = valueInput.value
|
91
|
+
}
|
92
|
+
})
|
93
|
+
return JSON.stringify(pairs)
|
94
|
+
}
|
95
|
+
|
96
|
+
// Get the current key-value pairs as an object
|
97
|
+
toObject() {
|
98
|
+
const pairs = {}
|
99
|
+
this.pairTargets.forEach(pair => {
|
100
|
+
const keyInput = pair.querySelector('[data-key-value-store-target="keyInput"]')
|
101
|
+
const valueInput = pair.querySelector('[data-key-value-store-target="valueInput"]')
|
102
|
+
|
103
|
+
if (keyInput && valueInput && keyInput.value.trim()) {
|
104
|
+
pairs[keyInput.value.trim()] = valueInput.value
|
105
|
+
}
|
106
|
+
})
|
107
|
+
return pairs
|
108
|
+
}
|
109
|
+
}
|
@@ -19,6 +19,7 @@ import AttachmentPreviewContainerController from "./attachment_preview_container
|
|
19
19
|
import SidebarController from "./sidebar_controller.js"
|
20
20
|
import PasswordVisibilityController from "./password_visibility_controller.js"
|
21
21
|
import RemoteModalController from "./remote_modal_controller.js"
|
22
|
+
import KeyValueStoreController from "./key_value_st\ore_controller.js"
|
22
23
|
|
23
24
|
export default function (application) {
|
24
25
|
// Register controllers here
|
@@ -42,4 +43,5 @@ export default function (application) {
|
|
42
43
|
application.register("attachment-preview", AttachmentPreviewController)
|
43
44
|
application.register("attachment-preview-container", AttachmentPreviewContainerController)
|
44
45
|
application.register("remote-modal", RemoteModalController)
|
46
|
+
application.register("key-value-store", KeyValueStoreController)
|
45
47
|
}
|
@@ -2,23 +2,30 @@ import { Controller } from "@hotwired/stimulus";
|
|
2
2
|
|
3
3
|
// Connects to data-controller="remote-modal"
|
4
4
|
export default class extends Controller {
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
connect() {
|
6
|
+
// Store original scroll position
|
7
|
+
this.originalScrollPosition = window.scrollY;
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
// Show the modal
|
10
|
+
this.element.showModal();
|
11
|
+
// Add close event listener
|
12
|
+
this.element.addEventListener("close", this.handleClose.bind(this));
|
13
|
+
}
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
close() {
|
16
|
+
// Close the modal
|
17
|
+
this.element.close();
|
18
|
+
// Restore the original scroll position
|
19
|
+
window.scrollTo(0, this.originalScrollPosition);
|
20
|
+
}
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
disconnect() {
|
23
|
+
// Clean up event listener when controller is disconnected
|
24
|
+
this.element.removeEventListener("close", this.handleClose);
|
25
|
+
}
|
26
|
+
|
27
|
+
handleClose() {
|
28
|
+
// Restore the original scroll position after dialog closes
|
29
|
+
window.scrollTo(0, this.originalScrollPosition);
|
30
|
+
}
|
31
|
+
}
|
@@ -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
|
-
|
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