admin_suite 0.2.7 → 0.2.8
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/CHANGELOG.md +17 -0
- data/app/helpers/admin_suite/base_helper.rb +56 -0
- data/app/javascript/admin_suite_application.js +44 -0
- data/app/javascript/controllers/admin_suite/dependent_searchable_select_controller.js +228 -0
- data/app/javascript/controllers/admin_suite/searchable_select_controller.js +1 -0
- data/app/views/layouts/admin_suite/application.html.erb +1 -0
- data/config/importmap.rb +1 -0
- data/lib/admin/base/resource.rb +3 -2
- data/lib/admin_suite/ui/field_renderer_registry.rb +4 -0
- data/lib/admin_suite/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 663de764696e7dc2696e54b4bb43381fe83ee33b74b9d212269ff6d25433466b
|
|
4
|
+
data.tar.gz: 9e9c6f145e86d4c8a0d1087cdc2d45c752da1fbbe9f1d4219d7b8874ebcc5291
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b23ab5d74fe3658980016cb8e7df4c1fe09a87601590b98b09dd43f3ece53c3d40f6cbc159aec035daec52525b8dbdbdf0da34ad8846061465ec9de05a270ae4
|
|
7
|
+
data.tar.gz: b70e3d18ec3aba0acd78fea6ee74efbec8201546253483abd00f1443e3411e37c9f98b065df6cf04f51ec3df28d9fa5e4ad90738781119f616c3c71db44f6130
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.8] - 2026-03-23
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Dependent searchable select** field type (`:dependent_select`) — cascading dropdowns where a child field's options filter by the selected parent value. Uses `parent_field:` option and `[label, value, group]` collection triples.
|
|
15
|
+
- **Stimulus entry point** (`admin_suite_application.js`) — automatically imports and registers all 12 AdminSuite Stimulus controllers on `window.Stimulus`.
|
|
16
|
+
- `parent_field` attribute on `FieldDefinition` for linking dependent fields.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **Stimulus controllers not loading** — engine controllers were pinned via importmap but never imported. Added explicit entry point in layout via `<script type="module">`.
|
|
21
|
+
- **Searchable select change propagation** — `applyOption()` now dispatches a `change` event on the hidden input, enabling dependent field chaining.
|
|
22
|
+
|
|
23
|
+
### Security
|
|
24
|
+
|
|
25
|
+
- New `dependent_searchable_select_controller.js` uses safe DOM construction (`createElement`/`textContent`/`appendChild`) instead of `innerHTML` template literals.
|
|
26
|
+
|
|
10
27
|
## [0.2.6] - 2026-02-21
|
|
11
28
|
|
|
12
29
|
### Added
|
|
@@ -1117,6 +1117,62 @@ module AdminSuite
|
|
|
1117
1117
|
end
|
|
1118
1118
|
end
|
|
1119
1119
|
|
|
1120
|
+
def render_dependent_searchable_select(_f, field, resource)
|
|
1121
|
+
param_key = resource.class.model_name.param_key
|
|
1122
|
+
current_value = resource.public_send(field.name)
|
|
1123
|
+
collection = field.collection.is_a?(Proc) ? field.collection.call : field.collection
|
|
1124
|
+
|
|
1125
|
+
# Collection format: [[label, value, group], ...] — triples with group for filtering
|
|
1126
|
+
all_options_json = if collection.is_a?(Array)
|
|
1127
|
+
collection.map { |opt|
|
|
1128
|
+
if opt.is_a?(Array) && opt.size >= 3
|
|
1129
|
+
{ value: opt[1], label: opt[0], group: opt[2] }
|
|
1130
|
+
elsif opt.is_a?(Array)
|
|
1131
|
+
{ value: opt[1], label: opt[0] }
|
|
1132
|
+
else
|
|
1133
|
+
{ value: opt, label: opt.to_s.humanize }
|
|
1134
|
+
end
|
|
1135
|
+
}.to_json
|
|
1136
|
+
else
|
|
1137
|
+
"[]"
|
|
1138
|
+
end
|
|
1139
|
+
|
|
1140
|
+
parent_selector = if field.parent_field
|
|
1141
|
+
"[name=\"#{param_key}[#{field.parent_field}]\"]"
|
|
1142
|
+
else
|
|
1143
|
+
""
|
|
1144
|
+
end
|
|
1145
|
+
|
|
1146
|
+
current_label = if current_value.present? && collection.is_a?(Array)
|
|
1147
|
+
match = collection.find { |opt| opt.is_a?(Array) ? opt[1].to_s == current_value.to_s : opt.to_s == current_value.to_s }
|
|
1148
|
+
match.is_a?(Array) ? match[0] : match.to_s
|
|
1149
|
+
else
|
|
1150
|
+
current_value
|
|
1151
|
+
end
|
|
1152
|
+
|
|
1153
|
+
content_tag(:div,
|
|
1154
|
+
data: {
|
|
1155
|
+
controller: "admin-suite--dependent-searchable-select",
|
|
1156
|
+
"admin-suite--dependent-searchable-select-all-options-value": all_options_json,
|
|
1157
|
+
"admin-suite--dependent-searchable-select-parent-selector-value": parent_selector
|
|
1158
|
+
},
|
|
1159
|
+
class: "relative") do
|
|
1160
|
+
concat(hidden_field_tag("#{param_key}[#{field.name}]", current_value,
|
|
1161
|
+
data: { "admin-suite--dependent-searchable-select-target": "input" }))
|
|
1162
|
+
concat(text_field_tag(nil, current_label,
|
|
1163
|
+
class: "form-input w-full",
|
|
1164
|
+
placeholder: field.placeholder || "Search...",
|
|
1165
|
+
autocomplete: "off",
|
|
1166
|
+
data: {
|
|
1167
|
+
"admin-suite--dependent-searchable-select-target": "search",
|
|
1168
|
+
action: "input->admin-suite--dependent-searchable-select#search focus->admin-suite--dependent-searchable-select#open keydown->admin-suite--dependent-searchable-select#keydown"
|
|
1169
|
+
}))
|
|
1170
|
+
concat(content_tag(:div, "",
|
|
1171
|
+
class: "absolute z-40 w-full mt-1 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg shadow-lg hidden max-h-60 overflow-y-auto",
|
|
1172
|
+
data: { "admin-suite--dependent-searchable-select-target": "dropdown" }))
|
|
1173
|
+
end
|
|
1174
|
+
end
|
|
1175
|
+
|
|
1120
1176
|
def render_multi_select(_f, field, resource)
|
|
1121
1177
|
param_key = resource.class.model_name.param_key
|
|
1122
1178
|
current_values =
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Application } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Reuse the host app's Stimulus application if available, otherwise start a new one
|
|
4
|
+
const application = window.Stimulus || Application.start()
|
|
5
|
+
window.Stimulus ||= application
|
|
6
|
+
|
|
7
|
+
import SearchableSelectController from "controllers/admin_suite/searchable_select_controller"
|
|
8
|
+
application.register("admin-suite--searchable-select", SearchableSelectController)
|
|
9
|
+
|
|
10
|
+
import ToggleSwitchController from "controllers/admin_suite/toggle_switch_controller"
|
|
11
|
+
application.register("admin-suite--toggle-switch", ToggleSwitchController)
|
|
12
|
+
|
|
13
|
+
import TagSelectController from "controllers/admin_suite/tag_select_controller"
|
|
14
|
+
application.register("admin-suite--tag-select", TagSelectController)
|
|
15
|
+
|
|
16
|
+
import FileUploadController from "controllers/admin_suite/file_upload_controller"
|
|
17
|
+
application.register("admin-suite--file-upload", FileUploadController)
|
|
18
|
+
|
|
19
|
+
import MarkdownEditorController from "controllers/admin_suite/markdown_editor_controller"
|
|
20
|
+
application.register("admin-suite--markdown-editor", MarkdownEditorController)
|
|
21
|
+
|
|
22
|
+
import JsonEditorController from "controllers/admin_suite/json_editor_controller"
|
|
23
|
+
application.register("admin-suite--json-editor", JsonEditorController)
|
|
24
|
+
|
|
25
|
+
import CodeEditorController from "controllers/admin_suite/code_editor_controller"
|
|
26
|
+
application.register("admin-suite--code-editor", CodeEditorController)
|
|
27
|
+
|
|
28
|
+
import LiveFilterController from "controllers/admin_suite/live_filter_controller"
|
|
29
|
+
application.register("admin-suite--live-filter", LiveFilterController)
|
|
30
|
+
|
|
31
|
+
import ClickActionsController from "controllers/admin_suite/click_actions_controller"
|
|
32
|
+
application.register("admin-suite--click-actions", ClickActionsController)
|
|
33
|
+
|
|
34
|
+
import ClipboardController from "controllers/admin_suite/clipboard_controller"
|
|
35
|
+
application.register("admin-suite--clipboard", ClipboardController)
|
|
36
|
+
|
|
37
|
+
import SidebarController from "controllers/admin_suite/sidebar_controller"
|
|
38
|
+
application.register("admin-suite--sidebar", SidebarController)
|
|
39
|
+
|
|
40
|
+
import FlashController from "controllers/admin_suite/flash_controller"
|
|
41
|
+
application.register("admin-suite--flash", FlashController)
|
|
42
|
+
|
|
43
|
+
import DependentSearchableSelectController from "controllers/admin_suite/dependent_searchable_select_controller"
|
|
44
|
+
application.register("admin-suite--dependent-searchable-select", DependentSearchableSelectController)
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dependent Searchable Select Controller (Admin Suite)
|
|
5
|
+
*
|
|
6
|
+
* Extends searchable-select behavior with parent-field filtering.
|
|
7
|
+
* Options are filtered by `group` matching the parent field's current value.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* data-controller="admin-suite--dependent-searchable-select"
|
|
11
|
+
* data-admin-suite--dependent-searchable-select-all-options-value="<%= options.to_json %>"
|
|
12
|
+
* data-admin-suite--dependent-searchable-select-parent-selector-value="#parent_field_id"
|
|
13
|
+
*
|
|
14
|
+
* Each option in allOptions should have: { value, label, group }
|
|
15
|
+
* The `group` field is matched against the parent field's value.
|
|
16
|
+
*/
|
|
17
|
+
export default class extends Controller {
|
|
18
|
+
static targets = ["input", "search", "dropdown"]
|
|
19
|
+
static values = {
|
|
20
|
+
allOptions: { type: Array, default: [] },
|
|
21
|
+
parentSelector: { type: String, default: "" },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
connect() {
|
|
25
|
+
this.isOpen = false
|
|
26
|
+
this.selectedIndex = -1
|
|
27
|
+
this.parentFilteredOptions = [...this.allOptionsValue]
|
|
28
|
+
|
|
29
|
+
this.clickOutside = this.clickOutside.bind(this)
|
|
30
|
+
document.addEventListener("click", this.clickOutside)
|
|
31
|
+
|
|
32
|
+
this.bindParent()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
disconnect() {
|
|
36
|
+
document.removeEventListener("click", this.clickOutside)
|
|
37
|
+
|
|
38
|
+
if (this._parentField && this._onParentChange) {
|
|
39
|
+
this._parentField.removeEventListener("change", this._onParentChange)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
bindParent() {
|
|
44
|
+
const selector = this.parentSelectorValue
|
|
45
|
+
if (!selector) return
|
|
46
|
+
|
|
47
|
+
this._parentField = document.querySelector(selector)
|
|
48
|
+
if (!this._parentField) return
|
|
49
|
+
|
|
50
|
+
this._onParentChange = this.onParentChange.bind(this)
|
|
51
|
+
this._parentField.addEventListener("change", this._onParentChange)
|
|
52
|
+
|
|
53
|
+
// Apply initial filter if parent already has a value
|
|
54
|
+
if (this._parentField.value) {
|
|
55
|
+
this.filterByParent(this._parentField.value)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
onParentChange() {
|
|
60
|
+
const parentValue = this._parentField ? this._parentField.value : ""
|
|
61
|
+
this.filterByParent(parentValue)
|
|
62
|
+
|
|
63
|
+
// Clear current selection if it no longer belongs to the new parent group
|
|
64
|
+
const currentValue = this.inputTarget.value
|
|
65
|
+
if (currentValue) {
|
|
66
|
+
const stillValid = this.parentFilteredOptions.some(
|
|
67
|
+
(opt) => String(opt.value) === String(currentValue),
|
|
68
|
+
)
|
|
69
|
+
if (!stillValid) {
|
|
70
|
+
this.inputTarget.value = ""
|
|
71
|
+
this.searchTarget.value = ""
|
|
72
|
+
this.inputTarget.dispatchEvent(new Event("change", { bubbles: true }))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (this.isOpen) {
|
|
77
|
+
this.renderDropdown()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
filterByParent(parentValue) {
|
|
82
|
+
if (!parentValue) {
|
|
83
|
+
this.parentFilteredOptions = [...this.allOptionsValue]
|
|
84
|
+
} else {
|
|
85
|
+
this.parentFilteredOptions = this.allOptionsValue.filter(
|
|
86
|
+
(opt) => String(opt.group) === String(parentValue),
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
open() {
|
|
92
|
+
this.isOpen = true
|
|
93
|
+
this.filteredOptions = [...this.parentFilteredOptions]
|
|
94
|
+
this.renderDropdown()
|
|
95
|
+
this.dropdownTarget.classList.remove("hidden")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
close() {
|
|
99
|
+
this.isOpen = false
|
|
100
|
+
this.dropdownTarget.classList.add("hidden")
|
|
101
|
+
this.selectedIndex = -1
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
clickOutside(event) {
|
|
105
|
+
if (!this.element.contains(event.target)) {
|
|
106
|
+
this.close()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
search() {
|
|
111
|
+
const query = this.searchTarget.value.toLowerCase().trim()
|
|
112
|
+
|
|
113
|
+
if (query) {
|
|
114
|
+
this.filteredOptions = this.parentFilteredOptions.filter((opt) =>
|
|
115
|
+
opt.label.toLowerCase().includes(query),
|
|
116
|
+
)
|
|
117
|
+
} else {
|
|
118
|
+
this.filteredOptions = [...this.parentFilteredOptions]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.renderDropdown()
|
|
122
|
+
|
|
123
|
+
if (!this.isOpen) this.open()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
select(event) {
|
|
127
|
+
const btn = event.currentTarget
|
|
128
|
+
const value = btn.dataset.value
|
|
129
|
+
const label = btn.dataset.label
|
|
130
|
+
|
|
131
|
+
this.inputTarget.value = value
|
|
132
|
+
this.searchTarget.value = label
|
|
133
|
+
this.close()
|
|
134
|
+
this.inputTarget.dispatchEvent(new Event("change", { bubbles: true }))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
keydown(event) {
|
|
138
|
+
const opts = this.filteredOptions || this.parentFilteredOptions
|
|
139
|
+
|
|
140
|
+
switch (event.key) {
|
|
141
|
+
case "ArrowDown":
|
|
142
|
+
event.preventDefault()
|
|
143
|
+
if (!this.isOpen) {
|
|
144
|
+
this.filteredOptions = [...this.parentFilteredOptions]
|
|
145
|
+
this.open()
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
this.selectedIndex = Math.min(this.selectedIndex + 1, opts.length - 1)
|
|
149
|
+
this.renderDropdown()
|
|
150
|
+
break
|
|
151
|
+
case "ArrowUp":
|
|
152
|
+
event.preventDefault()
|
|
153
|
+
this.selectedIndex = Math.max(this.selectedIndex - 1, 0)
|
|
154
|
+
this.renderDropdown()
|
|
155
|
+
break
|
|
156
|
+
case "Enter":
|
|
157
|
+
event.preventDefault()
|
|
158
|
+
if (this.selectedIndex >= 0 && opts[this.selectedIndex]) {
|
|
159
|
+
const opt = opts[this.selectedIndex]
|
|
160
|
+
this.inputTarget.value = opt.value
|
|
161
|
+
this.searchTarget.value = opt.label
|
|
162
|
+
this.close()
|
|
163
|
+
this.inputTarget.dispatchEvent(new Event("change", { bubbles: true }))
|
|
164
|
+
}
|
|
165
|
+
break
|
|
166
|
+
case "Escape":
|
|
167
|
+
this.close()
|
|
168
|
+
break
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
renderDropdown() {
|
|
173
|
+
const opts = this.filteredOptions || this.parentFilteredOptions
|
|
174
|
+
|
|
175
|
+
// Clear existing content using safe DOM manipulation (avoids innerHTML XSS risk)
|
|
176
|
+
while (this.dropdownTarget.firstChild) {
|
|
177
|
+
this.dropdownTarget.removeChild(this.dropdownTarget.firstChild)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!opts.length) {
|
|
181
|
+
const empty = document.createElement("div")
|
|
182
|
+
empty.className = "px-3 py-2 text-sm text-slate-400"
|
|
183
|
+
empty.textContent = "No results found"
|
|
184
|
+
this.dropdownTarget.appendChild(empty)
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
opts.forEach((opt, index) => {
|
|
189
|
+
const btn = document.createElement("button")
|
|
190
|
+
btn.type = "button"
|
|
191
|
+
btn.className = [
|
|
192
|
+
"block w-full text-left px-3 py-2 text-sm hover:bg-slate-100",
|
|
193
|
+
index === this.selectedIndex ? "bg-slate-100" : "",
|
|
194
|
+
"text-slate-700",
|
|
195
|
+
]
|
|
196
|
+
.filter(Boolean)
|
|
197
|
+
.join(" ")
|
|
198
|
+
btn.dataset.action =
|
|
199
|
+
"click->admin-suite--dependent-searchable-select#select"
|
|
200
|
+
btn.dataset.value = this.escapeAttr(String(opt.value))
|
|
201
|
+
btn.dataset.label = this.escapeAttr(String(opt.label))
|
|
202
|
+
|
|
203
|
+
// Label text node
|
|
204
|
+
btn.appendChild(document.createTextNode(opt.label))
|
|
205
|
+
|
|
206
|
+
// Optional group badge
|
|
207
|
+
if (opt.group) {
|
|
208
|
+
const badge = document.createElement("span")
|
|
209
|
+
badge.className =
|
|
210
|
+
"ml-2 text-xs text-slate-400 font-normal"
|
|
211
|
+
badge.textContent = opt.group
|
|
212
|
+
btn.appendChild(badge)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this.dropdownTarget.appendChild(btn)
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Escapes a string for use in HTML data attributes
|
|
220
|
+
escapeAttr(value) {
|
|
221
|
+
return String(value)
|
|
222
|
+
.replace(/&/g, "&")
|
|
223
|
+
.replace(/"/g, """)
|
|
224
|
+
.replace(/'/g, "'")
|
|
225
|
+
.replace(/</g, "<")
|
|
226
|
+
.replace(/>/g, ">")
|
|
227
|
+
}
|
|
228
|
+
}
|
data/config/importmap.rb
CHANGED
data/lib/admin/base/resource.rb
CHANGED
|
@@ -380,7 +380,8 @@ module Admin
|
|
|
380
380
|
preview: options[:preview] != false,
|
|
381
381
|
variants: options[:variants],
|
|
382
382
|
label_color: options[:label_color],
|
|
383
|
-
label_size: options[:label_size]
|
|
383
|
+
label_size: options[:label_size],
|
|
384
|
+
parent_field: options[:parent_field]
|
|
384
385
|
)
|
|
385
386
|
end
|
|
386
387
|
|
|
@@ -406,7 +407,7 @@ module Admin
|
|
|
406
407
|
:name, :type, :required, :label, :help, :placeholder,
|
|
407
408
|
:collection, :create_url, :accept, :rows, :readonly,
|
|
408
409
|
:if_condition, :unless_condition, :multiple, :creatable,
|
|
409
|
-
:preview, :variants, :label_color, :label_size,
|
|
410
|
+
:preview, :variants, :label_color, :label_size, :parent_field,
|
|
410
411
|
keyword_init: true
|
|
411
412
|
)
|
|
412
413
|
|
|
@@ -58,6 +58,10 @@ AdminSuite::UI::FieldRendererRegistry.register(:searchable_select) do |view, f,
|
|
|
58
58
|
view.render_searchable_select(f, field, resource)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
AdminSuite::UI::FieldRendererRegistry.register(:dependent_select) do |view, f, field, resource, _field_class|
|
|
62
|
+
view.render_dependent_searchable_select(f, field, resource)
|
|
63
|
+
end
|
|
64
|
+
|
|
61
65
|
AdminSuite::UI::FieldRendererRegistry.register(:multi_select) do |view, f, field, resource, _field_class|
|
|
62
66
|
view.render_multi_select(f, field, resource)
|
|
63
67
|
end
|
data/lib/admin_suite/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: admin_suite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- TechWright Labs
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-03-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -136,9 +136,11 @@ files:
|
|
|
136
136
|
- app/helpers/admin_suite/panels_helper.rb
|
|
137
137
|
- app/helpers/admin_suite/resources_helper.rb
|
|
138
138
|
- app/helpers/admin_suite/theme_helper.rb
|
|
139
|
+
- app/javascript/admin_suite_application.js
|
|
139
140
|
- app/javascript/controllers/admin_suite/click_actions_controller.js
|
|
140
141
|
- app/javascript/controllers/admin_suite/clipboard_controller.js
|
|
141
142
|
- app/javascript/controllers/admin_suite/code_editor_controller.js
|
|
143
|
+
- app/javascript/controllers/admin_suite/dependent_searchable_select_controller.js
|
|
142
144
|
- app/javascript/controllers/admin_suite/file_upload_controller.js
|
|
143
145
|
- app/javascript/controllers/admin_suite/flash_controller.js
|
|
144
146
|
- app/javascript/controllers/admin_suite/json_editor_controller.js
|