hotwire_nested_form 1.1.0 → 1.2.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/CHANGELOG.md +16 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +4 -1
- data/README.md +80 -2
- data/lib/generators/hotwire_nested_form/templates/nested_form_controller.js +73 -11
- data/lib/hotwire_nested_form/form_builder_detector.rb +11 -0
- data/lib/hotwire_nested_form/version.rb +1 -1
- data/npm/README.md +23 -4
- data/npm/package.json +2 -2
- data/npm/src/nested_form_controller.js +70 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 95575593f362dd0db15938a6b130eb2fa7353e12b4c939c84ee4aa1eb00035ec
|
|
4
|
+
data.tar.gz: 8ace6c3f971faaadc42d2692a6540bd4a907387302963573a904ad7990b1a3b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 409ccb4b58b6be6caba5a0b8b5634f999337677de6c146ea3bb100bb3bab9496dca69f4ae5c72ed2587419d321552bb4c7bfb09788ac7664069c0fd2e5ae7e0d
|
|
7
|
+
data.tar.gz: 4b5902764ddc0e1e7b3223cd6f449a325089198a4a0fadee4077638eb7b403bc0179f0105233950c63b73f828339c6b796f577e3d807e67ec66137e286784791
|
data/CHANGELOG.md
CHANGED
|
@@ -7,11 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.0] - 2026-02-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Dynamic min/max limits via data attributes
|
|
14
|
+
- `data-nested-form-min-value` - minimum items required
|
|
15
|
+
- `data-nested-form-max-value` - maximum items allowed
|
|
16
|
+
- `data-nested-form-limit-behavior-value` - behavior at limits: "disable", "hide", or "error"
|
|
17
|
+
- New events: `nested-form:limit-reached` and `nested-form:minimum-reached`
|
|
18
|
+
- Formtastic form builder auto-detection and compatibility
|
|
19
|
+
- `formtastic?` and `formtastic_available?` methods in FormBuilderDetector
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Stimulus controller now updates button states automatically
|
|
23
|
+
- Buttons disable/hide based on current count vs limits
|
|
24
|
+
|
|
10
25
|
## [1.1.0] - 2026-02-05
|
|
11
26
|
|
|
12
27
|
### Added
|
|
13
28
|
- SimpleForm auto-detection and compatibility
|
|
14
|
-
- NPM package
|
|
29
|
+
- NPM package `hotwire-nested-form-stimulus` for JavaScript-only users
|
|
15
30
|
- `FormBuilderDetector` module for form builder type detection
|
|
16
31
|
|
|
17
32
|
### Changed
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
hotwire_nested_form (1.
|
|
4
|
+
hotwire_nested_form (1.2.0)
|
|
5
5
|
rails (>= 7.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -105,6 +105,8 @@ GEM
|
|
|
105
105
|
drb (2.2.3)
|
|
106
106
|
erb (6.0.1)
|
|
107
107
|
erubi (1.13.1)
|
|
108
|
+
formtastic (5.0.0)
|
|
109
|
+
actionpack (>= 6.0.0)
|
|
108
110
|
globalid (1.3.0)
|
|
109
111
|
activesupport (>= 6.1)
|
|
110
112
|
i18n (1.14.8)
|
|
@@ -314,6 +316,7 @@ PLATFORMS
|
|
|
314
316
|
DEPENDENCIES
|
|
315
317
|
capybara (~> 3.39)
|
|
316
318
|
debug
|
|
319
|
+
formtastic (~> 5.0)
|
|
317
320
|
hotwire_nested_form!
|
|
318
321
|
puma (~> 6.0)
|
|
319
322
|
rails (~> 8.0.0)
|
data/README.md
CHANGED
|
@@ -104,19 +104,95 @@ Works automatically with SimpleForm! No configuration needed.
|
|
|
104
104
|
<% end %>
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
+
## Formtastic Support
|
|
108
|
+
|
|
109
|
+
Works automatically with Formtastic! No configuration needed.
|
|
110
|
+
|
|
111
|
+
```erb
|
|
112
|
+
<%= semantic_form_for @project do |f| %>
|
|
113
|
+
<%= f.input :name %>
|
|
114
|
+
|
|
115
|
+
<div data-controller="nested-form">
|
|
116
|
+
<%= f.semantic_fields_for :tasks do |task_form| %>
|
|
117
|
+
<%= render "task_fields", f: task_form %>
|
|
118
|
+
<% end %>
|
|
119
|
+
|
|
120
|
+
<%= link_to_add_association "Add Task", f, :tasks %>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<%= f.actions %>
|
|
124
|
+
<% end %>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Min/Max Limits
|
|
128
|
+
|
|
129
|
+
Control the number of nested items with data attributes:
|
|
130
|
+
|
|
131
|
+
```erb
|
|
132
|
+
<div data-controller="nested-form"
|
|
133
|
+
data-nested-form-min-value="1"
|
|
134
|
+
data-nested-form-max-value="5"
|
|
135
|
+
data-nested-form-limit-behavior-value="disable">
|
|
136
|
+
|
|
137
|
+
<%= f.fields_for :tasks do |tf| %>
|
|
138
|
+
<%= render "task_fields", f: tf %>
|
|
139
|
+
<% end %>
|
|
140
|
+
|
|
141
|
+
<%= link_to_add_association "Add Task", f, :tasks %>
|
|
142
|
+
</div>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Limit Options
|
|
146
|
+
|
|
147
|
+
| Attribute | Type | Default | Description |
|
|
148
|
+
|-----------|------|---------|-------------|
|
|
149
|
+
| `data-nested-form-min-value` | Integer | `0` | Minimum items required |
|
|
150
|
+
| `data-nested-form-max-value` | Integer | unlimited | Maximum items allowed |
|
|
151
|
+
| `data-nested-form-limit-behavior-value` | String | `"disable"` | `"disable"`, `"hide"`, or `"error"` |
|
|
152
|
+
|
|
153
|
+
### Limit Behaviors
|
|
154
|
+
|
|
155
|
+
| Behavior | At Max Limit | At Min Limit |
|
|
156
|
+
|----------|--------------|--------------|
|
|
157
|
+
| `disable` | Add button disabled | Remove buttons disabled |
|
|
158
|
+
| `hide` | Add button hidden | Remove buttons hidden |
|
|
159
|
+
| `error` | Event fires, button enabled | Event fires, button enabled |
|
|
160
|
+
|
|
161
|
+
### Dynamic Limits
|
|
162
|
+
|
|
163
|
+
Change limits at runtime via JavaScript:
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
const form = document.querySelector('[data-controller="nested-form"]')
|
|
167
|
+
form.dataset.nestedFormMaxValue = 10 // Change max
|
|
168
|
+
form.dataset.nestedFormMinValue = 2 // Change min
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Limit Events
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
document.addEventListener("nested-form:limit-reached", (event) => {
|
|
175
|
+
alert(`Maximum ${event.detail.limit} items allowed`)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
document.addEventListener("nested-form:minimum-reached", (event) => {
|
|
179
|
+
alert(`Must keep at least ${event.detail.minimum} items`)
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
107
183
|
## NPM Package (JavaScript-only)
|
|
108
184
|
|
|
109
185
|
For non-Rails projects using Stimulus, install via npm:
|
|
110
186
|
|
|
111
187
|
```bash
|
|
112
|
-
npm install
|
|
188
|
+
npm install hotwire-nested-form-stimulus
|
|
113
189
|
```
|
|
114
190
|
|
|
115
191
|
Register the controller:
|
|
116
192
|
|
|
117
193
|
```javascript
|
|
118
194
|
import { Application } from "@hotwired/stimulus"
|
|
119
|
-
import NestedFormController from "
|
|
195
|
+
import NestedFormController from "hotwire-nested-form-stimulus"
|
|
120
196
|
|
|
121
197
|
const application = Application.start()
|
|
122
198
|
application.register("nested-form", NestedFormController)
|
|
@@ -198,6 +274,8 @@ link_to_remove_association(name, form, options = {}, &block)
|
|
|
198
274
|
| `nested-form:after-add` | No | `{ wrapper }` | After fields added |
|
|
199
275
|
| `nested-form:before-remove` | Yes | `{ wrapper }` | Before removing fields |
|
|
200
276
|
| `nested-form:after-remove` | No | `{ wrapper }` | After fields removed |
|
|
277
|
+
| `nested-form:limit-reached` | No | `{ limit, current }` | When max limit reached |
|
|
278
|
+
| `nested-form:minimum-reached` | No | `{ minimum, current }` | When min limit reached |
|
|
201
279
|
|
|
202
280
|
**Usage Examples:**
|
|
203
281
|
|
|
@@ -1,31 +1,65 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
|
-
// Connects to data-controller="nested-form"
|
|
4
3
|
export default class extends Controller {
|
|
5
4
|
static values = {
|
|
6
|
-
wrapperClass: { type: String, default: "nested-fields" }
|
|
5
|
+
wrapperClass: { type: String, default: "nested-fields" },
|
|
6
|
+
min: { type: Number, default: 0 },
|
|
7
|
+
max: { type: Number, default: 999999 },
|
|
8
|
+
limitBehavior: { type: String, default: "disable" }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
connect() {
|
|
12
|
+
this.updateButtonStates()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get currentCount() {
|
|
16
|
+
return this.element.querySelectorAll(`.${this.wrapperClassValue}:not([style*="display: none"])`).length
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get addButtons() {
|
|
20
|
+
return this.element.querySelectorAll('[data-action*="nested-form#add"]')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get removeButtons() {
|
|
24
|
+
return this.element.querySelectorAll('[data-action*="nested-form#remove"]')
|
|
7
25
|
}
|
|
8
26
|
|
|
9
27
|
add(event) {
|
|
10
28
|
event.preventDefault()
|
|
11
29
|
|
|
30
|
+
if (this.currentCount >= this.maxValue) {
|
|
31
|
+
this.dispatch("limit-reached", {
|
|
32
|
+
detail: { limit: this.maxValue, current: this.currentCount }
|
|
33
|
+
})
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
12
37
|
const template = event.currentTarget.dataset.template
|
|
13
38
|
const insertion = event.currentTarget.dataset.insertion || "before"
|
|
14
39
|
const targetSelector = event.currentTarget.dataset.target
|
|
15
40
|
const count = parseInt(event.currentTarget.dataset.count) || 1
|
|
16
41
|
|
|
17
42
|
for (let i = 0; i < count; i++) {
|
|
18
|
-
this
|
|
43
|
+
if (this.currentCount >= this.maxValue) break
|
|
44
|
+
this.insertFields(template, insertion, targetSelector, event.currentTarget)
|
|
19
45
|
}
|
|
46
|
+
|
|
47
|
+
this.updateButtonStates()
|
|
20
48
|
}
|
|
21
49
|
|
|
22
50
|
remove(event) {
|
|
23
51
|
event.preventDefault()
|
|
24
52
|
|
|
53
|
+
if (this.currentCount <= this.minValue) {
|
|
54
|
+
this.dispatch("minimum-reached", {
|
|
55
|
+
detail: { minimum: this.minValue, current: this.currentCount }
|
|
56
|
+
})
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
25
60
|
const wrapper = event.currentTarget.closest(`.${this.wrapperClassValue}`)
|
|
26
61
|
if (!wrapper) return
|
|
27
62
|
|
|
28
|
-
// Dispatch cancelable before-remove event
|
|
29
63
|
const beforeEvent = this.dispatch("before-remove", {
|
|
30
64
|
cancelable: true,
|
|
31
65
|
detail: { wrapper }
|
|
@@ -36,27 +70,23 @@ export default class extends Controller {
|
|
|
36
70
|
const destroyInput = wrapper.querySelector("input[name*='_destroy']")
|
|
37
71
|
|
|
38
72
|
if (destroyInput) {
|
|
39
|
-
// Persisted record - hide and mark for destruction
|
|
40
73
|
destroyInput.value = "true"
|
|
41
74
|
wrapper.style.display = "none"
|
|
42
75
|
} else {
|
|
43
|
-
// New record - remove from DOM
|
|
44
76
|
wrapper.remove()
|
|
45
77
|
}
|
|
46
78
|
|
|
47
79
|
this.dispatch("after-remove", { detail: { wrapper } })
|
|
80
|
+
this.updateButtonStates()
|
|
48
81
|
}
|
|
49
82
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
#insertFields(template, insertion, targetSelector, trigger) {
|
|
83
|
+
insertFields(template, insertion, targetSelector, trigger) {
|
|
53
84
|
const newId = new Date().getTime()
|
|
54
85
|
const content = template.replace(/NEW_RECORD/g, newId)
|
|
55
86
|
|
|
56
87
|
const fragment = document.createRange().createContextualFragment(content)
|
|
57
88
|
const wrapper = fragment.firstElementChild
|
|
58
89
|
|
|
59
|
-
// Dispatch cancelable before-add event
|
|
60
90
|
const beforeEvent = this.dispatch("before-add", {
|
|
61
91
|
cancelable: true,
|
|
62
92
|
detail: { wrapper }
|
|
@@ -78,10 +108,42 @@ export default class extends Controller {
|
|
|
78
108
|
case "prepend":
|
|
79
109
|
container.prepend(fragment)
|
|
80
110
|
break
|
|
81
|
-
default:
|
|
111
|
+
default:
|
|
82
112
|
trigger.before(fragment)
|
|
83
113
|
}
|
|
84
114
|
|
|
85
115
|
this.dispatch("after-add", { detail: { wrapper } })
|
|
86
116
|
}
|
|
117
|
+
|
|
118
|
+
updateButtonStates() {
|
|
119
|
+
const atMax = this.currentCount >= this.maxValue
|
|
120
|
+
const atMin = this.currentCount <= this.minValue
|
|
121
|
+
|
|
122
|
+
this.addButtons.forEach(button => {
|
|
123
|
+
this.applyLimitState(button, atMax)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
this.removeButtons.forEach(button => {
|
|
127
|
+
const wrapper = button.closest(`.${this.wrapperClassValue}`)
|
|
128
|
+
if (wrapper && wrapper.style.display !== "none") {
|
|
129
|
+
this.applyLimitState(button, atMin)
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
applyLimitState(button, isAtLimit) {
|
|
135
|
+
switch (this.limitBehaviorValue) {
|
|
136
|
+
case "hide":
|
|
137
|
+
button.style.display = isAtLimit ? "none" : ""
|
|
138
|
+
button.disabled = false
|
|
139
|
+
break
|
|
140
|
+
case "error":
|
|
141
|
+
button.disabled = false
|
|
142
|
+
button.style.display = ""
|
|
143
|
+
break
|
|
144
|
+
default: // "disable"
|
|
145
|
+
button.disabled = isAtLimit
|
|
146
|
+
button.style.display = ""
|
|
147
|
+
}
|
|
148
|
+
}
|
|
87
149
|
}
|
|
@@ -11,8 +11,19 @@ module HotwireNestedForm
|
|
|
11
11
|
builder_class.include?('SimpleForm')
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
def formtastic?(form_builder)
|
|
15
|
+
return false unless form_builder
|
|
16
|
+
|
|
17
|
+
builder_class = form_builder.class.name.to_s
|
|
18
|
+
builder_class.include?('Formtastic')
|
|
19
|
+
end
|
|
20
|
+
|
|
14
21
|
def simple_form_available?
|
|
15
22
|
defined?(::SimpleForm) ? true : false
|
|
16
23
|
end
|
|
24
|
+
|
|
25
|
+
def formtastic_available?
|
|
26
|
+
defined?(::Formtastic) ? true : false
|
|
27
|
+
end
|
|
17
28
|
end
|
|
18
29
|
end
|
data/npm/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# hotwire-nested-form-stimulus
|
|
2
2
|
|
|
3
3
|
A Stimulus controller for dynamic nested forms. Add and remove nested form fields with ease.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install
|
|
8
|
+
npm install hotwire-nested-form-stimulus
|
|
9
9
|
# or
|
|
10
|
-
yarn add
|
|
10
|
+
yarn add hotwire-nested-form-stimulus
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
@@ -16,7 +16,7 @@ yarn add @hotwire-nested-form/stimulus
|
|
|
16
16
|
|
|
17
17
|
```javascript
|
|
18
18
|
import { Application } from "@hotwired/stimulus"
|
|
19
|
-
import NestedFormController from "
|
|
19
|
+
import NestedFormController from "hotwire-nested-form-stimulus"
|
|
20
20
|
|
|
21
21
|
const application = Application.start()
|
|
22
22
|
application.register("nested-form", NestedFormController)
|
|
@@ -47,6 +47,23 @@ application.register("nested-form", NestedFormController)
|
|
|
47
47
|
| `data-count` | Number of fields to add per click | `1` |
|
|
48
48
|
| `data-target` | CSS selector for insertion container | Parent element |
|
|
49
49
|
|
|
50
|
+
### Min/Max Limits
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<div data-controller="nested-form"
|
|
54
|
+
data-nested-form-min-value="1"
|
|
55
|
+
data-nested-form-max-value="5"
|
|
56
|
+
data-nested-form-limit-behavior-value="disable">
|
|
57
|
+
<!-- fields here -->
|
|
58
|
+
</div>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| Attribute | Description | Default |
|
|
62
|
+
|-----------|-------------|---------|
|
|
63
|
+
| `data-nested-form-min-value` | Minimum items required | `0` |
|
|
64
|
+
| `data-nested-form-max-value` | Maximum items allowed | unlimited |
|
|
65
|
+
| `data-nested-form-limit-behavior-value` | `"disable"`, `"hide"`, or `"error"` | `"disable"` |
|
|
66
|
+
|
|
50
67
|
### Events
|
|
51
68
|
|
|
52
69
|
| Event | Cancelable | Detail |
|
|
@@ -55,6 +72,8 @@ application.register("nested-form", NestedFormController)
|
|
|
55
72
|
| `nested-form:after-add` | No | `{ wrapper }` |
|
|
56
73
|
| `nested-form:before-remove` | Yes | `{ wrapper }` |
|
|
57
74
|
| `nested-form:after-remove` | No | `{ wrapper }` |
|
|
75
|
+
| `nested-form:limit-reached` | No | `{ limit, current }` |
|
|
76
|
+
| `nested-form:minimum-reached` | No | `{ minimum, current }` |
|
|
58
77
|
|
|
59
78
|
### Example: Listen for Events
|
|
60
79
|
|
data/npm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
3
|
-
"version": "1.
|
|
2
|
+
"name": "hotwire-nested-form-stimulus",
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Stimulus controller for dynamic nested forms - works with Rails, React, Vue, or any Stimulus app",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -2,25 +2,61 @@ import { Controller } from "@hotwired/stimulus"
|
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
4
|
static values = {
|
|
5
|
-
wrapperClass: { type: String, default: "nested-fields" }
|
|
5
|
+
wrapperClass: { type: String, default: "nested-fields" },
|
|
6
|
+
min: { type: Number, default: 0 },
|
|
7
|
+
max: { type: Number, default: 999999 },
|
|
8
|
+
limitBehavior: { type: String, default: "disable" }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
connect() {
|
|
12
|
+
this.updateButtonStates()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get currentCount() {
|
|
16
|
+
return this.element.querySelectorAll(`.${this.wrapperClassValue}:not([style*="display: none"])`).length
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get addButtons() {
|
|
20
|
+
return this.element.querySelectorAll('[data-action*="nested-form#add"]')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get removeButtons() {
|
|
24
|
+
return this.element.querySelectorAll('[data-action*="nested-form#remove"]')
|
|
6
25
|
}
|
|
7
26
|
|
|
8
27
|
add(event) {
|
|
9
28
|
event.preventDefault()
|
|
10
29
|
|
|
30
|
+
if (this.currentCount >= this.maxValue) {
|
|
31
|
+
this.dispatch("limit-reached", {
|
|
32
|
+
detail: { limit: this.maxValue, current: this.currentCount }
|
|
33
|
+
})
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
11
37
|
const template = event.currentTarget.dataset.template
|
|
12
38
|
const insertion = event.currentTarget.dataset.insertion || "before"
|
|
13
39
|
const targetSelector = event.currentTarget.dataset.target
|
|
14
40
|
const count = parseInt(event.currentTarget.dataset.count) || 1
|
|
15
41
|
|
|
16
42
|
for (let i = 0; i < count; i++) {
|
|
43
|
+
if (this.currentCount >= this.maxValue) break
|
|
17
44
|
this.insertFields(template, insertion, targetSelector, event.currentTarget)
|
|
18
45
|
}
|
|
46
|
+
|
|
47
|
+
this.updateButtonStates()
|
|
19
48
|
}
|
|
20
49
|
|
|
21
50
|
remove(event) {
|
|
22
51
|
event.preventDefault()
|
|
23
52
|
|
|
53
|
+
if (this.currentCount <= this.minValue) {
|
|
54
|
+
this.dispatch("minimum-reached", {
|
|
55
|
+
detail: { minimum: this.minValue, current: this.currentCount }
|
|
56
|
+
})
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
24
60
|
const wrapper = event.currentTarget.closest(`.${this.wrapperClassValue}`)
|
|
25
61
|
if (!wrapper) return
|
|
26
62
|
|
|
@@ -41,6 +77,7 @@ export default class extends Controller {
|
|
|
41
77
|
}
|
|
42
78
|
|
|
43
79
|
this.dispatch("after-remove", { detail: { wrapper } })
|
|
80
|
+
this.updateButtonStates()
|
|
44
81
|
}
|
|
45
82
|
|
|
46
83
|
insertFields(template, insertion, targetSelector, trigger) {
|
|
@@ -77,4 +114,36 @@ export default class extends Controller {
|
|
|
77
114
|
|
|
78
115
|
this.dispatch("after-add", { detail: { wrapper } })
|
|
79
116
|
}
|
|
117
|
+
|
|
118
|
+
updateButtonStates() {
|
|
119
|
+
const atMax = this.currentCount >= this.maxValue
|
|
120
|
+
const atMin = this.currentCount <= this.minValue
|
|
121
|
+
|
|
122
|
+
this.addButtons.forEach(button => {
|
|
123
|
+
this.applyLimitState(button, atMax)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
this.removeButtons.forEach(button => {
|
|
127
|
+
const wrapper = button.closest(`.${this.wrapperClassValue}`)
|
|
128
|
+
if (wrapper && wrapper.style.display !== "none") {
|
|
129
|
+
this.applyLimitState(button, atMin)
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
applyLimitState(button, isAtLimit) {
|
|
135
|
+
switch (this.limitBehaviorValue) {
|
|
136
|
+
case "hide":
|
|
137
|
+
button.style.display = isAtLimit ? "none" : ""
|
|
138
|
+
button.disabled = false
|
|
139
|
+
break
|
|
140
|
+
case "error":
|
|
141
|
+
button.disabled = false
|
|
142
|
+
button.style.display = ""
|
|
143
|
+
break
|
|
144
|
+
default: // "disable"
|
|
145
|
+
button.disabled = isAtLimit
|
|
146
|
+
button.style.display = ""
|
|
147
|
+
}
|
|
148
|
+
}
|
|
80
149
|
}
|