tramway 3.1 → 3.1.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.
- checksums.yaml +4 -4
- data/README.md +36 -5
- data/app/assets/javascripts/tramway/tramway.js +418 -0
- data/app/components/tramway/badge_component.rb +42 -6
- data/app/components/tramway/button_component.html.haml +30 -23
- data/app/components/tramway/button_component.rb +31 -1
- data/app/components/tramway/form/builder.rb +1 -1
- data/app/components/tramway/tooltip_component.html.haml +12 -0
- data/app/components/tramway/tooltip_component.rb +93 -0
- data/app/views/tramway/entities/_form.html.haml +1 -1
- data/config/tailwind.config.js +41 -1
- data/lib/generators/tramway/install/install_generator.rb +41 -29
- data/lib/tramway/engine.rb +3 -1
- data/lib/tramway/helpers/views_helper.rb +5 -1
- data/lib/tramway/version.rb +1 -1
- metadata +5 -5
- data/app/assets/javascripts/tramway/table_row_preview_controller.js +0 -175
- data/app/assets/javascripts/tramway/tramway-select_controller.js +0 -198
- data/app/assets/javascripts/tramway/ui_checkbox_controller.js +0 -36
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { Controller } from "@hotwired/stimulus"
|
|
2
|
-
|
|
3
|
-
export default class TramwaySelect extends Controller {
|
|
4
|
-
static targets = ["dropdown", "showSelectedArea", "hiddenInput", "caretDown", "caretUp"]
|
|
5
|
-
|
|
6
|
-
static values = {
|
|
7
|
-
items: Array,
|
|
8
|
-
dropdownContainer: String,
|
|
9
|
-
itemContainer: String,
|
|
10
|
-
selectedItemTemplate: String,
|
|
11
|
-
dropdownState: String,
|
|
12
|
-
selectedItems: Array,
|
|
13
|
-
placeholder: String,
|
|
14
|
-
selectAsInput: String,
|
|
15
|
-
value: Array,
|
|
16
|
-
onChange: String,
|
|
17
|
-
multiple: Boolean,
|
|
18
|
-
autocomplete: Boolean,
|
|
19
|
-
autocompleteInput: String
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
connect() {
|
|
23
|
-
this.dropdownState = 'closed';
|
|
24
|
-
|
|
25
|
-
this.items = JSON.parse(this.element.dataset.items).map((item, index) => {
|
|
26
|
-
return {
|
|
27
|
-
index,
|
|
28
|
-
text: item.text,
|
|
29
|
-
value: item.value.toString(),
|
|
30
|
-
selected: false
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const initialValues = this.element.dataset.value === undefined ? [] : JSON.parse(this.element.dataset.value);
|
|
35
|
-
|
|
36
|
-
initialValues.map((value) => {
|
|
37
|
-
const itemIndex = this.items.findIndex(x => x.value.toString() === value.toString());
|
|
38
|
-
this.items[itemIndex].selected = true;
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
this.selectedItems = this.items.filter(item => item.selected);
|
|
42
|
-
|
|
43
|
-
this.renderSelectedItems();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
renderSelectedItems() {
|
|
47
|
-
const allItems = this.fillTemplate(this.element.dataset.selectedItemTemplate, this.selectedItems)
|
|
48
|
-
|
|
49
|
-
let content = allItems;
|
|
50
|
-
|
|
51
|
-
if (this.autocomplete() && this.selectedItems.length === 0) {
|
|
52
|
-
content += this.element.dataset.autocompleteInput;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
this.showSelectedAreaTarget.innerHTML = content;
|
|
56
|
-
this.showSelectedAreaTarget.insertAdjacentHTML("beforeEnd", this.input());
|
|
57
|
-
this.updateInputOptions();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
fillTemplate(template, items) {
|
|
61
|
-
return items.map((item) => {
|
|
62
|
-
return template.replace(/{{text}}/g, item.text).replace(/{{value}}/g, item.value)
|
|
63
|
-
}).join('')
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
closeOnClickOutside(event) {
|
|
67
|
-
if (this.dropdownState === 'open' && !this.element.contains(event.target)) {
|
|
68
|
-
this.closeDropdown();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
toggleDropdown() {
|
|
73
|
-
if (this.dropdownState === 'closed') {
|
|
74
|
-
this.openDropdown();
|
|
75
|
-
} else {
|
|
76
|
-
this.closeDropdown();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
rerenderItems() {
|
|
81
|
-
this.closeDropdown();
|
|
82
|
-
this.openDropdown();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
openDropdown() {
|
|
86
|
-
this.dropdownState = 'open';
|
|
87
|
-
this.dropdownTarget.insertAdjacentHTML("afterend", this.template);
|
|
88
|
-
|
|
89
|
-
if (this.dropdown()) {
|
|
90
|
-
this.dropdown().addEventListener('click', event => event.stopPropagation());
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this.caretDownTarget.classList.add('hidden');
|
|
94
|
-
this.caretUpTarget.classList.remove('hidden');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
dropdown() {
|
|
98
|
-
return this.element.querySelector('#dropdown');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
closeDropdown() {
|
|
102
|
-
this.dropdownState = 'closed';
|
|
103
|
-
|
|
104
|
-
if (this.dropdown()) {
|
|
105
|
-
this.dropdown().remove();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const onChange = this.element.dataset.onChange;
|
|
109
|
-
|
|
110
|
-
if (onChange) {
|
|
111
|
-
const [controllerName, actionName] = onChange.split('#');
|
|
112
|
-
const controller = this.application.controllers.find(controller => controller.identifier === controllerName)
|
|
113
|
-
|
|
114
|
-
if (controller) {
|
|
115
|
-
if (typeof controller[actionName] === 'function') {
|
|
116
|
-
controller[actionName]({ target: this.element });
|
|
117
|
-
} else {
|
|
118
|
-
alert(`Action not found: ${actionName}`); // eslint-disable-line no-undef
|
|
119
|
-
}
|
|
120
|
-
} else {
|
|
121
|
-
alert(`Controller not found: ${controllerName}`); // eslint-disable-line no-undef
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
this.caretDownTarget.classList.remove('hidden');
|
|
126
|
-
this.caretUpTarget.classList.add('hidden');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
get template() {
|
|
130
|
-
return this.element.dataset.dropdownContainer.replace(
|
|
131
|
-
/{{content}}/g,
|
|
132
|
-
this.fillTemplate(this.element.dataset.itemContainer, this.items.filter(item => !item.selected))
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
toggleItem({ currentTarget }) {
|
|
137
|
-
const itemIndex = this.items.findIndex(x => x.value === currentTarget.dataset.value);
|
|
138
|
-
const itemSelectedIndex = this.selectedItems.findIndex(x => x.value === currentTarget.dataset.value);
|
|
139
|
-
|
|
140
|
-
if (!this.multiple()) {
|
|
141
|
-
this.selectedItems = [];
|
|
142
|
-
this.items.forEach(item => item.selected = false);
|
|
143
|
-
this.closeDropdown()
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (itemSelectedIndex !== -1) {
|
|
147
|
-
this.selectedItems = this.selectedItems.filter((_, index) => index !== itemSelectedIndex);
|
|
148
|
-
this.items[itemIndex].selected = false;
|
|
149
|
-
} else {
|
|
150
|
-
this.selectedItems.push(this.items[itemIndex]);
|
|
151
|
-
this.items[itemIndex].selected = true;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
this.renderSelectedItems();
|
|
155
|
-
|
|
156
|
-
if (this.multiple()) {
|
|
157
|
-
this.rerenderItems();
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
input() {
|
|
162
|
-
const placeholder = this.selectedItems.length > 0 ? '' : this.element.dataset.placeholder;
|
|
163
|
-
return this.element.dataset.selectAsInput.replace(/{{placeholder}}/g, placeholder);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
updateInputOptions() {
|
|
167
|
-
this.hiddenInputTarget.innerHTML = '';
|
|
168
|
-
this.selectedItems.forEach(selected => {
|
|
169
|
-
const option = document.createElement("option");
|
|
170
|
-
option.text = selected.text;
|
|
171
|
-
option.value = selected.value;
|
|
172
|
-
option.setAttribute("selected", true);
|
|
173
|
-
this.hiddenInputTarget.append(option);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
this.hiddenInputTarget.value = this.selectedItems.map(item => item.value);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
multiple() {
|
|
180
|
-
return this.element.dataset.multiple == 'true';
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
autocomplete() {
|
|
184
|
-
return this.element.dataset.autocomplete == 'true';
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
search(event) {
|
|
188
|
-
const searchTerm = event.target.value.toLowerCase();
|
|
189
|
-
const filteredItems = this.items.filter(item => item.text.toLowerCase().includes(searchTerm) && !item.selected);
|
|
190
|
-
const dropdown = this.dropdown();
|
|
191
|
-
|
|
192
|
-
if (dropdown) {
|
|
193
|
-
dropdown.innerHTML = this.fillTemplate(this.element.dataset.itemContainer, filteredItems);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export { TramwaySelect }
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { Controller } from "@hotwired/stimulus"
|
|
2
|
-
|
|
3
|
-
export default class UiCheckbox extends Controller {
|
|
4
|
-
static targets = ["input", "button", "indicator"]
|
|
5
|
-
|
|
6
|
-
connect() {
|
|
7
|
-
this.sync()
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
toggle(event) {
|
|
11
|
-
event.preventDefault()
|
|
12
|
-
|
|
13
|
-
if (this.inputTarget.disabled) return
|
|
14
|
-
|
|
15
|
-
this.inputTarget.click()
|
|
16
|
-
this.sync()
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
sync() {
|
|
20
|
-
const checked = this.inputTarget.checked
|
|
21
|
-
const state = checked ? "checked" : "unchecked"
|
|
22
|
-
|
|
23
|
-
this.buttonTarget.setAttribute("aria-checked", checked.toString())
|
|
24
|
-
this.buttonTarget.dataset.state = state
|
|
25
|
-
this.buttonTarget.classList.toggle("border-zinc-50", checked)
|
|
26
|
-
this.buttonTarget.classList.toggle("bg-zinc-50", checked)
|
|
27
|
-
this.buttonTarget.classList.toggle("text-zinc-950", checked)
|
|
28
|
-
this.buttonTarget.classList.toggle("border-zinc-800", !checked)
|
|
29
|
-
this.buttonTarget.classList.toggle("bg-zinc-950", !checked)
|
|
30
|
-
this.buttonTarget.classList.toggle("text-zinc-50", !checked)
|
|
31
|
-
this.indicatorTarget.classList.toggle("hidden", !checked)
|
|
32
|
-
this.buttonTarget.toggleAttribute("disabled", this.inputTarget.disabled)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export { UiCheckbox }
|