hotwire_nested_form 1.0.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: feaf2e256fd22252ab8270a20cfdf0cf77fff4c0937c937be19fa07d6a0dee73
4
- data.tar.gz: c77e047a69062a5a37f4634cf0950e80f812c0d7af70c72d2df9f63fd827213b
3
+ metadata.gz: 614ce7e4bb9ee960608d18431bd94319feac263ce6a83f79bc36525f5c2d5061
4
+ data.tar.gz: 3c8c196d819cf4954fbedf3444070a95244636427bb73147d3edc71465d5afb2
5
5
  SHA512:
6
- metadata.gz: 603c4baba0d02740cd5fa54d2002c5580c16a7fc4e2de3909917d97bd99d9b08976daff5dd74ccc9bd01c40e4a8a9936d0a8f637f1eb05d4a65682295fab1b33
7
- data.tar.gz: ac9266803d3881bd3794799bf870f95a6c569b796407b8f3318c11b5dc8562b898b41849e614996c18141a1c24c5aab1f6a3a535fa58f6eed9d3fa622e1cdb53
6
+ metadata.gz: 5ec8765adbc4c27dbd3297f6497971f29a22872381c114c12ce03c7ee8649e0e47e6c500be06c7f1ee71a3512f7897f9a94bc96f95474e730874828188784579
7
+ data.tar.gz: d90976447adf75d242bdb1f200af575e3724036b7441876e48134a28f4abd7c603cc62cb9c997679a8234b19b3a6a3465e77e51fb944a144b25b0f4c4e1dc7ad
data/.rubocop.yml CHANGED
@@ -107,6 +107,15 @@ RSpec/DescribeClass:
107
107
  RSpec/SpecFilePathFormat:
108
108
  Exclude:
109
109
  - 'spec/helpers/**/*'
110
+ - 'spec/lib/**/*'
111
+ - 'spec/integration/**/*'
112
+
113
+ # Allow regular doubles when mocking classes that may not exist (e.g., SimpleForm)
114
+ RSpec/VerifiedDoubles:
115
+ Exclude:
116
+ - 'spec/lib/**/*'
117
+ - 'spec/helpers/**/*'
118
+ - 'spec/integration/**/*'
110
119
 
111
120
  RSpec/LetSetup:
112
121
  Exclude:
data/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.0] - 2026-02-05
11
+
12
+ ### Added
13
+ - SimpleForm auto-detection and compatibility
14
+ - NPM package `@hotwire-nested-form/stimulus` for JavaScript-only users
15
+ - `FormBuilderDetector` module for form builder type detection
16
+
17
+ ### Changed
18
+ - Improved documentation for SimpleForm usage
19
+
10
20
  ## [1.0.0] - 2026-02-05
11
21
 
12
22
  ### Added
data/Gemfile CHANGED
@@ -14,6 +14,7 @@ group :development, :test do
14
14
  gem 'puma', '~> 6.0'
15
15
  gem 'rspec-rails', '~> 6.0'
16
16
  gem 'selenium-webdriver', '~> 4.10'
17
+ gem 'simple_form', '~> 5.3'
17
18
  gem 'sqlite3', '>= 1.6'
18
19
 
19
20
  # Code quality
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hotwire_nested_form (1.0.0)
4
+ hotwire_nested_form (1.1.0)
5
5
  rails (>= 7.0)
6
6
 
7
7
  GEM
@@ -270,6 +270,9 @@ GEM
270
270
  rexml (~> 3.2, >= 3.2.5)
271
271
  rubyzip (>= 1.2.2, < 4.0)
272
272
  websocket (~> 1.0)
273
+ simple_form (5.4.1)
274
+ actionpack (>= 7.0)
275
+ activemodel (>= 7.0)
273
276
  sqlite3 (2.9.0-aarch64-linux-gnu)
274
277
  sqlite3 (2.9.0-aarch64-linux-musl)
275
278
  sqlite3 (2.9.0-arm-linux-gnu)
@@ -319,6 +322,7 @@ DEPENDENCIES
319
322
  rubocop-rails
320
323
  rubocop-rspec
321
324
  selenium-webdriver (~> 4.10)
325
+ simple_form (~> 5.3)
322
326
  sqlite3 (>= 1.6)
323
327
 
324
328
  BUNDLED WITH
data/README.md CHANGED
@@ -84,6 +84,46 @@ end
84
84
 
85
85
  That's it! Click "Add Task" to add fields, "Remove" to remove them.
86
86
 
87
+ ## SimpleForm Support
88
+
89
+ Works automatically with SimpleForm! No configuration needed.
90
+
91
+ ```erb
92
+ <%= simple_form_for @project do |f| %>
93
+ <%= f.input :name %>
94
+
95
+ <div data-controller="nested-form">
96
+ <%= f.simple_fields_for :tasks do |task_form| %>
97
+ <%= render "task_fields", f: task_form %>
98
+ <% end %>
99
+
100
+ <%= link_to_add_association "Add Task", f, :tasks %>
101
+ </div>
102
+
103
+ <%= f.button :submit %>
104
+ <% end %>
105
+ ```
106
+
107
+ ## NPM Package (JavaScript-only)
108
+
109
+ For non-Rails projects using Stimulus, install via npm:
110
+
111
+ ```bash
112
+ npm install @hotwire-nested-form/stimulus
113
+ ```
114
+
115
+ Register the controller:
116
+
117
+ ```javascript
118
+ import { Application } from "@hotwired/stimulus"
119
+ import NestedFormController from "@hotwire-nested-form/stimulus"
120
+
121
+ const application = Application.start()
122
+ application.register("nested-form", NestedFormController)
123
+ ```
124
+
125
+ See [NPM package documentation](npm/README.md) for full details.
126
+
87
127
  ## API Reference
88
128
 
89
129
  ### link_to_add_association
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HotwireNestedForm
4
+ module FormBuilderDetector
5
+ module_function
6
+
7
+ def simple_form?(form_builder)
8
+ return false unless form_builder
9
+
10
+ builder_class = form_builder.class.name.to_s
11
+ builder_class.include?('SimpleForm')
12
+ end
13
+
14
+ def simple_form_available?
15
+ defined?(::SimpleForm) ? true : false
16
+ end
17
+ end
18
+ end
@@ -78,7 +78,9 @@ module HotwireNestedForm
78
78
  # Determine partial name
79
79
  partial_name = partial || "#{association.to_s.singularize}_fields"
80
80
 
81
- # Render the fields
81
+ # Render the fields using fields_for
82
+ # This works with both standard Rails FormBuilder and SimpleForm::FormBuilder
83
+ # SimpleForm overrides fields_for to use simple_fields_for internally
82
84
  form.fields_for(association, new_object, child_index: 'NEW_RECORD') do |builder|
83
85
  locals = (render_options[:locals] || {}).merge(f: builder)
84
86
  render(partial: partial_name, locals: locals)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HotwireNestedForm
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -3,6 +3,7 @@
3
3
  require_relative 'hotwire_nested_form/version'
4
4
  require_relative 'hotwire_nested_form/engine'
5
5
  require_relative 'hotwire_nested_form/helpers'
6
+ require_relative 'hotwire_nested_form/form_builder_detector'
6
7
 
7
8
  module HotwireNestedForm
8
9
  class Error < StandardError; end
data/npm/.npmignore ADDED
@@ -0,0 +1,6 @@
1
+ # Ignore everything except src
2
+ *
3
+ !src/
4
+ !src/**
5
+ !README.md
6
+ !package.json
data/npm/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # @hotwire-nested-form/stimulus
2
+
3
+ A Stimulus controller for dynamic nested forms. Add and remove nested form fields with ease.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @hotwire-nested-form/stimulus
9
+ # or
10
+ yarn add @hotwire-nested-form/stimulus
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Register the Controller
16
+
17
+ ```javascript
18
+ import { Application } from "@hotwired/stimulus"
19
+ import NestedFormController from "@hotwire-nested-form/stimulus"
20
+
21
+ const application = Application.start()
22
+ application.register("nested-form", NestedFormController)
23
+ ```
24
+
25
+ ### HTML Structure
26
+
27
+ ```html
28
+ <div data-controller="nested-form">
29
+ <div id="items">
30
+ <!-- Existing nested fields go here -->
31
+ </div>
32
+
33
+ <a href="#"
34
+ data-action="nested-form#add"
35
+ data-template="<div class='nested-fields'><input name='items[][name]'><a href='#' data-action='nested-form#remove'>Remove</a></div>">
36
+ Add Item
37
+ </a>
38
+ </div>
39
+ ```
40
+
41
+ ### Data Attributes
42
+
43
+ | Attribute | Description | Default |
44
+ |-----------|-------------|---------|
45
+ | `data-template` | HTML template for new fields (use `NEW_RECORD` as placeholder) | Required |
46
+ | `data-insertion` | Where to insert: `before`, `after`, `append`, `prepend` | `before` |
47
+ | `data-count` | Number of fields to add per click | `1` |
48
+ | `data-target` | CSS selector for insertion container | Parent element |
49
+
50
+ ### Events
51
+
52
+ | Event | Cancelable | Detail |
53
+ |-------|------------|--------|
54
+ | `nested-form:before-add` | Yes | `{ wrapper }` |
55
+ | `nested-form:after-add` | No | `{ wrapper }` |
56
+ | `nested-form:before-remove` | Yes | `{ wrapper }` |
57
+ | `nested-form:after-remove` | No | `{ wrapper }` |
58
+
59
+ ### Example: Listen for Events
60
+
61
+ ```javascript
62
+ document.addEventListener("nested-form:after-add", (event) => {
63
+ console.log("Added:", event.detail.wrapper)
64
+ })
65
+
66
+ document.addEventListener("nested-form:before-remove", (event) => {
67
+ if (!confirm("Are you sure?")) {
68
+ event.preventDefault()
69
+ }
70
+ })
71
+ ```
72
+
73
+ ### With Rails
74
+
75
+ For Rails users, we recommend using the [hotwire_nested_form](https://rubygems.org/gems/hotwire_nested_form) gem which provides view helpers.
76
+
77
+ ## License
78
+
79
+ MIT
data/npm/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@hotwire-nested-form/stimulus",
3
+ "version": "1.1.0",
4
+ "description": "Stimulus controller for dynamic nested forms - works with Rails, React, Vue, or any Stimulus app",
5
+ "main": "src/index.js",
6
+ "module": "src/index.js",
7
+ "type": "module",
8
+ "files": [
9
+ "src"
10
+ ],
11
+ "keywords": [
12
+ "stimulus",
13
+ "hotwire",
14
+ "nested-forms",
15
+ "rails",
16
+ "dynamic-forms",
17
+ "cocoon-alternative"
18
+ ],
19
+ "author": "BhumitBhadani <bhumit2520@gmail.com>",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/bhumit4220/hotwire_nested_form.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/bhumit4220/hotwire_nested_form/issues"
27
+ },
28
+ "homepage": "https://github.com/bhumit4220/hotwire_nested_form#readme",
29
+ "peerDependencies": {
30
+ "@hotwired/stimulus": "^3.0.0"
31
+ }
32
+ }
data/npm/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { default as NestedFormController } from "./nested_form_controller.js"
2
+ export { default } from "./nested_form_controller.js"
@@ -0,0 +1,80 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ wrapperClass: { type: String, default: "nested-fields" }
6
+ }
7
+
8
+ add(event) {
9
+ event.preventDefault()
10
+
11
+ const template = event.currentTarget.dataset.template
12
+ const insertion = event.currentTarget.dataset.insertion || "before"
13
+ const targetSelector = event.currentTarget.dataset.target
14
+ const count = parseInt(event.currentTarget.dataset.count) || 1
15
+
16
+ for (let i = 0; i < count; i++) {
17
+ this.insertFields(template, insertion, targetSelector, event.currentTarget)
18
+ }
19
+ }
20
+
21
+ remove(event) {
22
+ event.preventDefault()
23
+
24
+ const wrapper = event.currentTarget.closest(`.${this.wrapperClassValue}`)
25
+ if (!wrapper) return
26
+
27
+ const beforeEvent = this.dispatch("before-remove", {
28
+ cancelable: true,
29
+ detail: { wrapper }
30
+ })
31
+
32
+ if (beforeEvent.defaultPrevented) return
33
+
34
+ const destroyInput = wrapper.querySelector("input[name*='_destroy']")
35
+
36
+ if (destroyInput) {
37
+ destroyInput.value = "true"
38
+ wrapper.style.display = "none"
39
+ } else {
40
+ wrapper.remove()
41
+ }
42
+
43
+ this.dispatch("after-remove", { detail: { wrapper } })
44
+ }
45
+
46
+ insertFields(template, insertion, targetSelector, trigger) {
47
+ const newId = new Date().getTime()
48
+ const content = template.replace(/NEW_RECORD/g, newId)
49
+
50
+ const fragment = document.createRange().createContextualFragment(content)
51
+ const wrapper = fragment.firstElementChild
52
+
53
+ const beforeEvent = this.dispatch("before-add", {
54
+ cancelable: true,
55
+ detail: { wrapper }
56
+ })
57
+
58
+ if (beforeEvent.defaultPrevented) return
59
+
60
+ const container = targetSelector
61
+ ? document.querySelector(targetSelector)
62
+ : trigger.parentElement
63
+
64
+ switch (insertion) {
65
+ case "after":
66
+ trigger.after(fragment)
67
+ break
68
+ case "append":
69
+ container.append(fragment)
70
+ break
71
+ case "prepend":
72
+ container.prepend(fragment)
73
+ break
74
+ default:
75
+ trigger.before(fragment)
76
+ }
77
+
78
+ this.dispatch("after-add", { detail: { wrapper } })
79
+ }
80
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hotwire_nested_form
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - BhumitBhadani
@@ -47,10 +47,16 @@ files:
47
47
  - lib/generators/hotwire_nested_form/templates/nested_form_controller.js
48
48
  - lib/hotwire_nested_form.rb
49
49
  - lib/hotwire_nested_form/engine.rb
50
+ - lib/hotwire_nested_form/form_builder_detector.rb
50
51
  - lib/hotwire_nested_form/helpers.rb
51
52
  - lib/hotwire_nested_form/helpers/add_association.rb
52
53
  - lib/hotwire_nested_form/helpers/remove_association.rb
53
54
  - lib/hotwire_nested_form/version.rb
55
+ - npm/.npmignore
56
+ - npm/README.md
57
+ - npm/package.json
58
+ - npm/src/index.js
59
+ - npm/src/nested_form_controller.js
54
60
  homepage: https://github.com/bhumit4220/hotwire_nested_form
55
61
  licenses:
56
62
  - MIT