cocooned 2.4.1 → 3.0.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: a39782348816fe6d41942f786eb178890583e6a94980f99b726c19fa6c9eb4b5
4
- data.tar.gz: 6837f2d09cecd7d6e3588a258308c784e9cddbb498b781a4c1692cced332c1a0
3
+ metadata.gz: 4452a44114a2a49166816f4966764b27cccf5de838568acb29fc68544fb96878
4
+ data.tar.gz: 7d28fe11d1d06bb28433be2587411471ce79db0983347de07acf40a2e62bc3d7
5
5
  SHA512:
6
- metadata.gz: 0d54e9ea05d221bdbd8dfb6546236ccaaa9972f2dcbfa232b751b08ad7f6b6f0195579d526070f7264bea97d9f44878ab3427c6f345b6ac6f7b3657458e12a31
7
- data.tar.gz: 354d3a47a177fe27d863e92119a46525d7526d5d656ea151dcf57194e00b0dd00f9e498a0c3fedff82a6c78bb5280ffb5f01a5e780e81c654425986406ea5d4a
6
+ metadata.gz: 8af0b42463b95aac1f4bf2c1f290a75735a1ebb304fe333f79124da55bb2f0d694c05e23292b3328accb0b70c596205b918a6912757f2e8dbddcd5101f6df4bc
7
+ data.tar.gz: e95c020441e9c26e4941d49ec7e202dc22af173b02af6bf2023a46260004dceee1e0ca9f352bed232a80b085c819a35f26d9994013a21a76ed65f669d2f4f471
data/CHANGELOG.md CHANGED
@@ -6,6 +6,36 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## Version 3.0.0 (2026-01-27)
10
+
11
+ ## Breaking changes
12
+
13
+ * Respect browser accessibility settings on reduced motion to determinate the default value of the `animate` option (#120)
14
+ `animate` will be set to false by default if the `(prefers-reduced-motion: reduce)` media query matches.
15
+ * Proper instances cleanup through [explicit resource management](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Resource_management) (#115)
16
+ Introduce a `dispose` method on Cocooned instances to clean up what was done in the `start` method: remove event listeners and delete references to the disposed instance, so it can be cleanly garbage collected.
17
+ This may require you to install a polyfill for `DisposableStack` and `[Symbol.dispose]()` to support older browsers.
18
+
19
+ To not introduce too many breaking changes in a single release, removal of long-time deprecated features have been postponed to Cocooned 4.0 (#122)
20
+
21
+ ## Added
22
+
23
+ * Optional Stimulus integration (#119)
24
+ Introduce two ways to integrate Cocooned with Stimulus: `registerCocoonedContainer` and `useCocooned`. See documentation for details.
25
+
26
+ ### Changed
27
+
28
+ * Add Ruby 4.0 to the test matrix (#112)
29
+
30
+ ## Version 2.5.0 (2025-10-23)
31
+
32
+ * Upgrade to use `eslint ~> 9` (#103)
33
+ * Configure dependabot to auto-update Github Actions (#99)
34
+ * Drop support for Rails 7.1, Ruby 2.7 and Ruby 3.1 (#98)
35
+ * Add Rails 8.1 to the test matrix (#96)
36
+ * Remove Rails 7.0 from the test matrix (#92)
37
+ * Add Ruby 3.4 to the test matrix (#88)
38
+
9
39
  ## Version 2.4.1 (2024-11-25)
10
40
 
11
41
  ### Changed
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Cocooned makes it easier to handle nested forms in Rails.
7
7
 
8
- Cocooned is form builder-agnostic: it works with standard Rails (>= 7.0, < 8.0) form helpers, [Formtastic](https://github.com/justinfrench/formtastic) or [SimpleForm](https://github.com/plataformatec/simple_form).
8
+ Cocooned is form builder-agnostic: it works with standard Rails (>= 7.0, < 8.2) form helpers, [Formtastic](https://github.com/justinfrench/formtastic) or [SimpleForm](https://github.com/plataformatec/simple_form).
9
9
 
10
10
  1. [Background](#some-background)
11
11
  2. [Installation](#installation)
@@ -23,7 +23,7 @@ Cocooned is a fork of [Cocoon](https://github.com/nathanvda/cocoon) by [Nathan V
23
23
 
24
24
  However, the project seems to have only received minimal fixes since 2018 and many pull requests, even simple ones, have been on hold for a long time. In 2019, as I needed more than what Cocoon provided at this time, I had the choice to either maintain an extension or to fork it and integrate everything that was waiting and more.
25
25
 
26
- Over the time, Cocooned turned into an almost complete rewrite of Cocoon with more functionnalities, a more fluent API (I hope) and integration with modern toolchains. Still, **Cocooned is completely compatible with Cocoon and can be used as a drop-in replacement** as long as we talk about Ruby code. Change the name of the gem in your Gemfile and you're done. **This compatibility layer with the original Cocoon API will be dropped in the next major release.**
26
+ Over the time, Cocooned turned into an almost complete rewrite of Cocoon with more functionnalities, a more fluent API (I hope) and integration with modern toolchains. Still, **Cocooned is compatible with Cocoon and can be used as a**(n almost) **drop-in replacement. This compatibility layer with the original Cocoon API will be dropped in the next major release.**
27
27
 
28
28
  On the JavaScript side, Cocooned 2.0 removed the dependency to jQuery (Yeah! :tada:). See [JavaScript](#javascript) for details.
29
29
 
@@ -40,15 +40,18 @@ gem 'cocooned'
40
40
  Cocooned comes with an NPM companion package: [`@notus.sh/cocooned`](https://www.npmjs.com/package/@notus.sh/cocooned).
41
41
  It bundles JavaScript files to handles in-browser interactions with your nested forms.
42
42
 
43
- If you use import maps (Rails 7.0+ default), add it with:
43
+ Install it with your favorite package manager:
44
44
 
45
45
  ```shell
46
+ # bun
47
+ $ bun install @notus.sh/cocooned
48
+ # importmap (Rails ~7.0 default)
46
49
  $ bin/importmap pin @notus.sh/cocooned
47
- ```
48
-
49
- If you use Yarn and Webpack (Rails 5.1+ default), add it with:
50
-
51
- ```shell
50
+ # npm
51
+ $ npm install @notus.sh/cocooned
52
+ # pnpm
53
+ $ pnpm add @notus.sh/cocooned
54
+ # yarn
52
55
  $ yarn add @notus.sh/cocooned
53
56
  ```
54
57
 
@@ -1,3 +1,3 @@
1
1
  //= require 'cocooned'
2
2
 
3
- /* TODO: Remove in 3.0 */
3
+ /* TODO: Remove in 4.0 */
@@ -36,6 +36,153 @@
36
36
  }
37
37
  }
38
38
 
39
+ class Traverser {
40
+ constructor (origin, traversal) {
41
+ this.#origin = origin;
42
+ this.#traversal = traversal;
43
+ }
44
+
45
+ resolve (selector) {
46
+ if (this.#traversal in this.#origin && typeof this.#origin[this.#traversal] === 'function') {
47
+ return this._tryMethod(this.#traversal, selector)
48
+ }
49
+
50
+ if (this.#traversal in this.#origin) {
51
+ return this._tryProperty(this.#traversal)
52
+ }
53
+
54
+ const method = `_${this.#traversal}`;
55
+ if (method in this) {
56
+ return this[method](selector)
57
+ }
58
+
59
+ return null
60
+ }
61
+
62
+ /* Protected and private attributes and methods */
63
+ #origin
64
+ #traversal
65
+
66
+ _tryMethod (method, selector) {
67
+ try {
68
+ const resolved = this.#origin[method](selector);
69
+ if (resolved instanceof HTMLElement) {
70
+ return resolved
71
+ }
72
+ } catch (e) {}
73
+
74
+ return null
75
+ }
76
+
77
+ _tryProperty (property) {
78
+ const resolved = this.#origin[property];
79
+ if (resolved instanceof HTMLElement) {
80
+ return resolved
81
+ }
82
+
83
+ return null
84
+ }
85
+
86
+ _parent (selector) {
87
+ if (this.#origin.parentElement.matches(selector)) {
88
+ return this.#origin.parentElement
89
+ }
90
+ return null
91
+ }
92
+
93
+ _prev (selector) {
94
+ if (this.#origin.previousElementSibling.matches(selector)) {
95
+ return this.#origin.previousElementSibling
96
+ }
97
+ return null
98
+ }
99
+
100
+ _next (selector) {
101
+ if (this.#origin.nextElementSibling.matches(selector)) {
102
+ return this.#origin.nextElementSibling
103
+ }
104
+ return null
105
+ }
106
+
107
+ _siblings (selector) {
108
+ return this.#origin.parentElement.querySelector(selector)
109
+ }
110
+ }
111
+
112
+ class Deprecator {
113
+ logger
114
+ package
115
+ version
116
+
117
+ constructor (version, packageName, logger) {
118
+ this.version = version;
119
+ this.package = packageName;
120
+ this.logger = logger;
121
+ }
122
+
123
+ warn (message, replacement = null) {
124
+ if (message in this.#emitted) {
125
+ return
126
+ }
127
+
128
+ const warning = `${message}. It will be removed from ${this.package} ${this.version}`;
129
+ const alternative = (replacement !== null ? `, use ${replacement} instead` : '');
130
+ this.logger.warn(`DEPRECATION WARNING: ${warning}${alternative}.`);
131
+
132
+ this.#emitted[message] = true;
133
+ }
134
+
135
+ /* Protected and private attributes and methods */
136
+ #emitted = Object.create(null)
137
+ }
138
+
139
+ const deprecators = Object.create(null);
140
+
141
+ function deprecator (version, packageName = 'Cocooned', logger = console) {
142
+ const hash = [version, packageName].join('#');
143
+ if (!(hash in deprecators)) {
144
+ deprecators[hash] = new Deprecator(version, packageName, logger);
145
+ }
146
+
147
+ return deprecators[hash]
148
+ }
149
+
150
+ class Listener {
151
+ constructor (eventTarget, type, listener) {
152
+ this.#eventTarget = eventTarget;
153
+ this.#type = type;
154
+ this.#listener = listener;
155
+
156
+ this.#eventTarget.addEventListener(this.#type, this.#listener);
157
+ }
158
+
159
+ dispose () {
160
+ this.#eventTarget.removeEventListener(this.#type, this.#listener);
161
+ }
162
+
163
+ /* Protected and private attributes and methods */
164
+ #eventTarget
165
+ #type
166
+ #listener
167
+ }
168
+
169
+ disposable(Listener);
170
+
171
+ if (typeof Symbol.dispose !== "symbol") {
172
+ console.warn(`
173
+ Cocooned use Disposable objects but they are not supported by your browser.
174
+ See Cocooned documentation for polyfill options.
175
+ `);
176
+ }
177
+
178
+ function disposable(klass) {
179
+ if (typeof Symbol.dispose !== "symbol") {
180
+ return
181
+ }
182
+
183
+ klass.prototype[Symbol.dispose] = klass.prototype.dispose;
184
+ }
185
+
39
186
  // Borrowed from <https://stackoverflow.com/a/2117523>
40
187
  function uuidv4 () {
41
188
  return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
@@ -69,13 +216,22 @@
69
216
  ]
70
217
  }
71
218
 
219
+ const canAnimate = (
220
+ 'animate' in document.createElement('div') &&
221
+ typeof document.createElement('div').animate === 'function'
222
+ );
223
+
224
+ const shouldAnimate = (
225
+ 'matchMedia' in window &&
226
+ !window.matchMedia('(prefers-reduced-motion: reduce)').matches
227
+ );
228
+
72
229
  const instances = Object.create(null);
73
230
 
74
231
  class Base {
75
232
  static get defaultOptions () {
76
- const element = document.createElement('div');
77
233
  return {
78
- animate: ('animate' in element && typeof element.animate === 'function'),
234
+ animate: canAnimate && shouldAnimate,
79
235
  animator: defaultAnimator,
80
236
  duration: 450
81
237
  }
@@ -98,7 +254,7 @@
98
254
 
99
255
  constructor (container, options) {
100
256
  this._container = container;
101
- this._uuid = uuidv4();
257
+ this.__uuid = uuidv4();
102
258
  this._options = this.constructor._normalizeOptions({
103
259
  ...this.constructor.defaultOptions,
104
260
  ...('cocoonedOptions' in container.dataset ? JSON.parse(container.dataset.cocoonedOptions) : {}),
@@ -115,16 +271,31 @@
115
271
  }
116
272
 
117
273
  start () {
118
- this.container.dataset.cocoonedContainer = true;
119
- this.container.dataset.cocoonedUuid = this._uuid;
120
- instances[this._uuid] = this;
274
+ if (!('cocoonedContainer' in this.container.dataset)) {
275
+ deprecator('4.0').warn(
276
+ 'CSS classes based detection is deprecated',
277
+ 'cocooned_container Rails helper to declare containers'
278
+ );
279
+ this.container.dataset.cocoonedContainer = true;
280
+ }
281
+
282
+ this.container.dataset.cocoonedUuid = this.__uuid;
283
+ this._onDispose(() => delete this.container.dataset.cocoonedUuid);
284
+
285
+ instances[this.__uuid] = this;
286
+ this._onDispose(() => delete instances[this.__uuid]);
121
287
 
122
288
  const hideDestroyed = () => { hideMarkedForDestruction(this, this.items); };
123
289
 
124
290
  hideDestroyed();
125
- this.container.ownerDocument.addEventListener('page:load', hideDestroyed);
126
- this.container.ownerDocument.addEventListener('turbo:load', hideDestroyed);
127
- this.container.ownerDocument.addEventListener('turbolinks:load', hideDestroyed);
291
+ this._addEventListener(this.container.ownerDocument, 'page:load', hideDestroyed);
292
+ this._addEventListener(this.container.ownerDocument, 'turbo:load', hideDestroyed);
293
+ this._addEventListener(this.container.ownerDocument, 'turbolinks:load', hideDestroyed);
294
+ }
295
+
296
+ dispose () {
297
+ this._disposer.dispose();
298
+ this._container = null;
128
299
  }
129
300
 
130
301
  notify (node, eventType, eventData) {
@@ -180,8 +351,21 @@
180
351
 
181
352
  _container
182
353
  _options
183
- __uuid
354
+ __disposer
184
355
  __emitter
356
+ __uuid
357
+
358
+ _addEventListener (target, type, listener) {
359
+ this._disposer.use(new Listener(target, type, listener));
360
+ }
361
+
362
+ get _disposer () {
363
+ if (typeof this.__disposer === 'undefined') {
364
+ this.__disposer = new DisposableStack();
365
+ }
366
+
367
+ return this.__disposer
368
+ }
185
369
 
186
370
  get _emitter () {
187
371
  if (typeof this.__emitter === 'undefined') {
@@ -191,6 +375,10 @@
191
375
  return this.__emitter
192
376
  }
193
377
 
378
+ _onDispose (callback) {
379
+ this._disposer.defer(callback);
380
+ }
381
+
194
382
  _selectors (name) {
195
383
  return this.constructor.selectors[name]
196
384
  }
@@ -205,6 +393,8 @@
205
393
  }
206
394
  }
207
395
 
396
+ disposable(Base);
397
+
208
398
  class Trigger {
209
399
  constructor (trigger, cocooned) {
210
400
  this._trigger = trigger;
@@ -277,117 +467,6 @@
277
467
  }
278
468
  }
279
469
 
280
- class Traverser {
281
- constructor (origin, traversal) {
282
- this.#origin = origin;
283
- this.#traversal = traversal;
284
- }
285
-
286
- resolve (selector) {
287
- if (this.#traversal in this.#origin && typeof this.#origin[this.#traversal] === 'function') {
288
- return this._tryMethod(this.#traversal, selector)
289
- }
290
-
291
- if (this.#traversal in this.#origin) {
292
- return this._tryProperty(this.#traversal)
293
- }
294
-
295
- const method = `_${this.#traversal}`;
296
- if (method in this) {
297
- return this[method](selector)
298
- }
299
-
300
- return null
301
- }
302
-
303
- /* Protected and private attributes and methods */
304
- #origin
305
- #traversal
306
-
307
- _tryMethod (method, selector) {
308
- try {
309
- const resolved = this.#origin[method](selector);
310
- if (resolved instanceof HTMLElement) {
311
- return resolved
312
- }
313
- } catch (e) {}
314
-
315
- return null
316
- }
317
-
318
- _tryProperty (property) {
319
- const resolved = this.#origin[property];
320
- if (resolved instanceof HTMLElement) {
321
- return resolved
322
- }
323
-
324
- return null
325
- }
326
-
327
- _parent (selector) {
328
- if (this.#origin.parentElement.matches(selector)) {
329
- return this.#origin.parentElement
330
- }
331
- return null
332
- }
333
-
334
- _prev (selector) {
335
- if (this.#origin.previousElementSibling.matches(selector)) {
336
- return this.#origin.previousElementSibling
337
- }
338
- return null
339
- }
340
-
341
- _next (selector) {
342
- if (this.#origin.nextElementSibling.matches(selector)) {
343
- return this.#origin.nextElementSibling
344
- }
345
- return null
346
- }
347
-
348
- _siblings (selector) {
349
- return this.#origin.parentElement.querySelector(selector)
350
- }
351
- }
352
-
353
- class Deprecator {
354
- logger
355
- package
356
- version
357
-
358
- constructor (version, packageName, logger) {
359
- this.version = version;
360
- this.package = packageName;
361
- this.logger = logger;
362
- }
363
-
364
- warn (message, replacement = null) {
365
- if (message in this.#emitted) {
366
- return
367
- }
368
-
369
- const warning = `${message}. It will be removed from ${this.package} ${this.version}`;
370
- const alternative = (replacement !== null ? `, use ${replacement} instead` : '');
371
- this.logger.warn(`DEPRECATION WARNING: ${warning}${alternative}.`);
372
-
373
- this.#emitted[message] = true;
374
- }
375
-
376
- /* Protected and private attributes and methods */
377
- #emitted = Object.create(null)
378
- }
379
-
380
- const deprecators = Object.create(null);
381
-
382
- function deprecator (version, packageName = 'Cocooned', logger = console) {
383
- const hash = [version, packageName].join('#');
384
- if (!(hash in deprecators)) {
385
- deprecators[hash] = new Deprecator(version, packageName, logger);
386
- }
387
-
388
- return deprecators[hash]
389
- }
390
-
391
470
  class Extractor {
392
471
  constructor (trigger, cocooned) {
393
472
  this.#trigger = trigger;
@@ -466,7 +545,7 @@
466
545
  return this.#trigger.ownerDocument.querySelector(node)
467
546
  }
468
547
 
469
- deprecator('3.0').warn('associationInsertionTraversal is deprecated');
548
+ deprecator('4.0').warn('associationInsertionTraversal is deprecated');
470
549
  const traverser = new Traverser(this.#trigger, this.#dataset.associationInsertionTraversal);
471
550
 
472
551
  return traverser.resolve(node)
@@ -736,12 +815,12 @@
736
815
  .map(element => Add.create(element, this))
737
816
  .filter(trigger => this.toContainer(trigger.insertionNode) === this.container);
738
817
 
739
- this.addTriggers.forEach(add => add.trigger.addEventListener(
740
- 'click',
741
- clickHandler$1((e) => add.handle(e))
742
- ));
818
+ this.addTriggers.forEach(add => {
819
+ this._addEventListener(add.trigger, 'click', clickHandler$1((e) => add.handle(e)));
820
+ });
743
821
 
744
- this.container.addEventListener(
822
+ this._addEventListener(
823
+ this.container,
745
824
  'click',
746
825
  itemDelegatedClickHandler(this, this._selector('triggers.remove'), (e) => {
747
826
  const trigger = new Remove(e.target, this);
@@ -795,7 +874,7 @@
795
874
  return
796
875
  }
797
876
 
798
- this.container.addEventListener('cocooned:before-insert', e => {
877
+ this._addEventListener(this.container, 'cocooned:before-insert', e => {
799
878
  if (this.items.length < this.options.limit) {
800
879
  return
801
880
  }
@@ -943,16 +1022,16 @@
943
1022
  return
944
1023
  }
945
1024
 
946
- this.container.addEventListener('cocooned:after-insert', e => this._reindexer.reindex(e));
947
- this.container.addEventListener('cocooned:after-remove', e => this._reindexer.reindex(e));
948
- this.container.addEventListener('cocooned:after-move', e => this._reindexer.reindex(e));
1025
+ this._addEventListener(this.container, 'cocooned:after-insert', e => this._reindexer.reindex(e));
1026
+ this._addEventListener(this.container, 'cocooned:after-remove', e => this._reindexer.reindex(e));
1027
+ this._addEventListener(this.container, 'cocooned:after-move', e => this._reindexer.reindex(e));
949
1028
  const form = this.container.closest('form');
950
1029
  if (form !== null) {
951
- form.addEventListener('submit', e => this._reindexer.reindex(e));
1030
+ this._addEventListener(form, 'submit', e => this._reindexer.reindex(e));
952
1031
  }
953
1032
 
954
- this.container.addEventListener('click', clickHandler(this, this._selector('triggers.up'), Up));
955
- this.container.addEventListener('click', clickHandler(this, this._selector('triggers.down'), Down));
1033
+ this._addEventListener(this.container, 'click', clickHandler(this, this._selector('triggers.up'), Up));
1034
+ this._addEventListener(this.container, 'click', clickHandler(this, this._selector('triggers.down'), Down));
956
1035
  }
957
1036
 
958
1037
  /* Protected and private attributes and methods */
@@ -1051,7 +1130,7 @@
1051
1130
 
1052
1131
  $(() => cocoonAutoStart($));
1053
1132
 
1054
- deprecator('3.0').warn(
1133
+ deprecator('4.0').warn(
1055
1134
  'Loading @notus.sh/cocooned/cocooned is deprecated',
1056
1135
  '@notus.sh/cocooned/jquery, @notus.sh/cocooned or `@notus.sh/cocooned/src/cocooned/cocooned`'
1057
1136
  );
@@ -2,4 +2,4 @@
2
2
  *= require cocooned
3
3
  */
4
4
 
5
- /* TODO: Remove in 3.0 */
5
+ /* TODO: Remove in 4.0 */
@@ -1 +1 @@
1
- /* TODO: Remove in 3.0 */
1
+ /* TODO: Remove in 4.0 */
data/cocooned.gemspec CHANGED
@@ -13,8 +13,8 @@ Gem::Specification.new do |spec|
13
13
 
14
14
  spec.summary = 'Form builder agnostic handling of Rails nested forms'
15
15
  spec.description = <<-DESC.gsub(/\s+/, ' ')
16
- Easier nested form in Rails with capabilities to add, remove, reorder or limit nested items.
17
- Works with standard Rails form builder, Formtastic or SimpleForm, and with or without jQuery.
16
+ Rails nested form made easy, with standard Rails form builder, Formtastic or SimpleForm.
17
+ Unobtrusive and zero-dependencies JavaScript included, with optional integration with jQuery and Stimulus.
18
18
  DESC
19
19
  spec.homepage = 'https://github.com/notus-sh/cocooned'
20
20
 
@@ -38,11 +38,11 @@ Gem::Specification.new do |spec|
38
38
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
39
39
  f.match(excluded_dirs) || excluded_files.include?(f)
40
40
  end
41
- spec.required_ruby_version = '>= 2.7'
41
+ spec.required_ruby_version = '>= 3.1'
42
42
 
43
- spec.add_dependency 'rails', '>= 7.0', '< 8.1'
43
+ spec.add_dependency 'rails', '>= 7.2', '< 8.2'
44
44
 
45
- spec.add_development_dependency 'bundler', '~> 2.1'
45
+ spec.add_development_dependency 'bundler', '>= 2.1'
46
46
  spec.add_development_dependency 'rake', '~> 13.0'
47
47
  spec.add_development_dependency 'rspec', '~> 3.11'
48
48
  end
@@ -26,14 +26,14 @@ module Cocooned
26
26
  cocooned_add_item_link(...)
27
27
  end
28
28
  deprecate link_to_add_association: 'Use :cocooned_add_link instead',
29
- deprecator: Deprecation['3.0']
29
+ deprecator: Deprecation['4.0']
30
30
 
31
31
  # @deprecated: Please use {#cocooned_remove_item_link} instead
32
32
  def link_to_remove_association(...)
33
33
  cocooned_remove_item_link(...)
34
34
  end
35
35
  deprecate link_to_remove_association: 'Use :cocooned_remove_item_link instead',
36
- deprecator: Deprecation['3.0']
36
+ deprecator: Deprecation['4.0']
37
37
  end
38
38
  end
39
39
 
@@ -44,7 +44,7 @@ module Cocooned
44
44
  def i18n_namespaces
45
45
  return super unless I18n.exists?(:cocoon)
46
46
 
47
- Deprecation['3.0'].warn 'Support for the :cocoon i18n namespace will be removed in 3.0', caller_locations(3)
47
+ Deprecation['4.0'].warn 'Support for the :cocoon i18n namespace will be removed in 4.0', caller_locations(3)
48
48
  super + %w[cocoon]
49
49
  end
50
50
  end
@@ -57,7 +57,7 @@ module Cocooned
57
57
  def html_data
58
58
  return super unless data_keys.size.positive?
59
59
 
60
- Deprecation['3.0'].warn 'Compatibility with options named data-* will be removed in 3.0', caller_locations(3)
60
+ Deprecation['4.0'].warn 'Compatibility with options named data-* will be removed in 4.0', caller_locations(3)
61
61
  html_data_normalize super.merge(data_options)
62
62
  end
63
63
 
@@ -78,7 +78,7 @@ module Cocooned
78
78
 
79
79
  def association_options
80
80
  if options.key? :insertion_traversal
81
- Deprecation['3.0'].warn 'Support for the :insertion_traversal will be removed in 3.0', caller_locations(3)
81
+ Deprecation['4.0'].warn 'Support for the :insertion_traversal will be removed in 4.0', caller_locations(3)
82
82
  end
83
83
 
84
84
  super
@@ -91,7 +91,7 @@ module Cocooned
91
91
  def renderer_options
92
92
  return super unless options.key?(:render_options)
93
93
 
94
- Deprecation['3.0'].warn 'Support for :render_options will be removed in 3.0', caller_locations(3)
94
+ Deprecation['4.0'].warn 'Support for :render_options will be removed in 4.0', caller_locations(3)
95
95
  legacy_options = options.delete(:render_options)
96
96
 
97
97
  super.tap do |opts|
@@ -29,13 +29,11 @@ module Cocooned
29
29
  #
30
30
  # Any other argument or option supported by `ActionView::Base#content_tag`
31
31
  # will be forwarded.
32
- def cocooned_container(*args, &block)
32
+ def cocooned_container(*args, &)
33
33
  options = args.extract_options!.dup
34
34
  name = args.shift || :div
35
- defaults = cocooned_wrapper_defaults(options, %w[cocooned-container], :'cocooned-container')
36
- defaults[:data][:cocooned_options] = options.extract!(:limit, :reorderable).to_json
37
35
 
38
- content_tag(name, *args, **options.deep_merge(defaults), &block)
36
+ content_tag(name, *args, **cocooned_container_options(options), &)
39
37
  end
40
38
 
41
39
  # Wrap content with the expected markup for a Cocooned item.
@@ -53,20 +51,33 @@ module Cocooned
53
51
  #
54
52
  # Any argument or option supported by `ActionView::Base#content_tag` will
55
53
  # be forwarded.
56
- def cocooned_item(*args, &block)
54
+ def cocooned_item(*args, &)
57
55
  options = args.extract_options!.dup
58
56
  name = args.shift || :div
59
- defaults = cocooned_wrapper_defaults(options, %w[cocooned-item nested-fields], :'cocooned-item')
60
57
 
61
- content_tag(name, *args, **options.deep_merge(defaults), &block)
58
+ content_tag(name, *args, **cocooned_item_options(options), &)
62
59
  end
63
60
 
64
61
  protected
65
62
 
66
- def cocooned_wrapper_defaults(options, additional_classes, mark)
67
- classes = Array.wrap(options.delete(:class)).flat_map { |k| k.to_s.split }.compact_blank
63
+ def cocooned_container_options(options)
64
+ options.deep_merge(
65
+ class: token_list(options.delete(:class), %w[cocooned-container]),
66
+ data: {
67
+ controller: [options.dig(:data, :controller), :cocooned].compact_blank.map(&:to_s).join(' '),
68
+ cocooned_container: true,
69
+ cocooned_options: options.extract!(:limit, :reorderable).to_json
70
+ }
71
+ )
72
+ end
68
73
 
69
- { class: (classes + additional_classes), data: { mark => true } }
74
+ def cocooned_item_options(options)
75
+ options.deep_merge(
76
+ class: token_list(options.delete(:class), %w[cocooned-item nested-fields]),
77
+ data: {
78
+ cocooned_item: true
79
+ }
80
+ )
70
81
  end
71
82
  end
72
83
  end
@@ -107,8 +107,8 @@ module Cocooned
107
107
  #
108
108
  # See the documentation of +ActionView::Base#link_to+ for additional
109
109
  # options.
110
- def cocooned_add_item_link(*args, &block)
111
- cocooned_link(Cocooned::Tags::Add, *args, &block)
110
+ def cocooned_add_item_link(...)
111
+ cocooned_link(Cocooned::Tags::Add, ...)
112
112
  end
113
113
 
114
114
  # Output a button to add an item in a nested form.
@@ -127,8 +127,8 @@ module Cocooned
127
127
  #
128
128
  # See Cocooned::Helpers::Tags::Add main documentation for a reference of
129
129
  # supported options.
130
- def cocooned_add_item_button(*args, &block)
131
- cocooned_button(Cocooned::Tags::Add, *args, &block)
130
+ def cocooned_add_item_button(...)
131
+ cocooned_button(Cocooned::Tags::Add, ...)
132
132
  end
133
133
  end
134
134
  end
@@ -44,8 +44,8 @@ module Cocooned
44
44
  #
45
45
  # See the documentation of +ActionView::Base#link_to+ for additional
46
46
  # options.
47
- def cocooned_move_item_down_link(*args, &block)
48
- cocooned_link(Cocooned::Tags::Down, *args, &block)
47
+ def cocooned_move_item_down_link(...)
48
+ cocooned_link(Cocooned::Tags::Down, ...)
49
49
  end
50
50
 
51
51
  # Output a button to move an item down.
@@ -67,8 +67,8 @@ module Cocooned
67
67
  #
68
68
  # See the documentation of +ActionView::Helpers::FormBuilder#button+ for
69
69
  # valid options.
70
- def cocooned_move_item_down_button(*args, &block)
71
- cocooned_button(Cocooned::Tags::Down, *args, &block)
70
+ def cocooned_move_item_down_button(...)
71
+ cocooned_button(Cocooned::Tags::Down, ...)
72
72
  end
73
73
  end
74
74
  end
@@ -45,8 +45,8 @@ module Cocooned
45
45
  #
46
46
  # See the documentation of +ActionView::Base#link_to+ for additional
47
47
  # options.
48
- def cocooned_remove_item_link(*args, &block)
49
- cocooned_link(Cocooned::Tags::Remove, *args, &block)
48
+ def cocooned_remove_item_link(...)
49
+ cocooned_link(Cocooned::Tags::Remove, ...)
50
50
  end
51
51
 
52
52
  # Output a button to remove an item (and an hidden field to mark
@@ -69,8 +69,8 @@ module Cocooned
69
69
  #
70
70
  # See the documentation of +ActionView::Base#link_to+ for additional
71
71
  # options.
72
- def cocooned_remove_item_button(*args, &block)
73
- cocooned_button(Cocooned::Tags::Remove, *args, &block)
72
+ def cocooned_remove_item_button(...)
73
+ cocooned_button(Cocooned::Tags::Remove, ...)
74
74
  end
75
75
  end
76
76
  end
@@ -44,8 +44,8 @@ module Cocooned
44
44
  #
45
45
  # See the documentation of +ActionView::Base#link_to+ for additional
46
46
  # options.
47
- def cocooned_move_item_up_link(*args, &block)
48
- cocooned_link(Cocooned::Tags::Up, *args, &block)
47
+ def cocooned_move_item_up_link(...)
48
+ cocooned_link(Cocooned::Tags::Up, ...)
49
49
  end
50
50
 
51
51
  # Output a button to move an item up.
@@ -67,8 +67,8 @@ module Cocooned
67
67
  #
68
68
  # See the documentation of +ActionView::Helpers::FormBuilder#button+ for
69
69
  # valid options.
70
- def cocooned_move_item_up_button(*args, &block)
71
- cocooned_button(Cocooned::Tags::Up, *args, &block)
70
+ def cocooned_move_item_up_button(...)
71
+ cocooned_button(Cocooned::Tags::Up, ...)
72
72
  end
73
73
  end
74
74
  end
@@ -46,14 +46,14 @@ module Cocooned
46
46
 
47
47
  protected
48
48
 
49
- def cocooned_link(klass, *args, &block)
49
+ def cocooned_link(klass, *args, &)
50
50
  options = args.extract_options!
51
- klass.create(self, *args, **options, &block).render(as: :link)
51
+ klass.create(self, *args, **options, &).render(as: :link)
52
52
  end
53
53
 
54
- def cocooned_button(klass, *args, &block)
54
+ def cocooned_button(klass, *args, &)
55
55
  options = args.extract_options!
56
- klass.create(self, *args, **options, &block).render(as: :button)
56
+ klass.create(self, *args, **options, &).render(as: :button)
57
57
  end
58
58
  end
59
59
  end
@@ -25,9 +25,9 @@ module Cocooned
25
25
  include AssociationOptions
26
26
  include Cocooned::Deprecated::TagsHelper::AssociationOptions
27
27
 
28
- def initialize(template, form, association, **options, &block)
28
+ def initialize(template, form, association, ...)
29
29
  @association = association
30
- super(template, form, **options, &block)
30
+ super(template, form, ...)
31
31
  end
32
32
 
33
33
  def render(as: :link)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cocooned
4
- VERSION = '2.4.1'
4
+ VERSION = '3.0.0'
5
5
  end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cocooned
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gaël-Ian Havard
8
8
  - Nathan Van der Auwera
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2024-11-25 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rails
@@ -17,32 +16,32 @@ dependencies:
17
16
  requirements:
18
17
  - - ">="
19
18
  - !ruby/object:Gem::Version
20
- version: '7.0'
19
+ version: '7.2'
21
20
  - - "<"
22
21
  - !ruby/object:Gem::Version
23
- version: '8.1'
22
+ version: '8.2'
24
23
  type: :runtime
25
24
  prerelease: false
26
25
  version_requirements: !ruby/object:Gem::Requirement
27
26
  requirements:
28
27
  - - ">="
29
28
  - !ruby/object:Gem::Version
30
- version: '7.0'
29
+ version: '7.2'
31
30
  - - "<"
32
31
  - !ruby/object:Gem::Version
33
- version: '8.1'
32
+ version: '8.2'
34
33
  - !ruby/object:Gem::Dependency
35
34
  name: bundler
36
35
  requirement: !ruby/object:Gem::Requirement
37
36
  requirements:
38
- - - "~>"
37
+ - - ">="
39
38
  - !ruby/object:Gem::Version
40
39
  version: '2.1'
41
40
  type: :development
42
41
  prerelease: false
43
42
  version_requirements: !ruby/object:Gem::Requirement
44
43
  requirements:
45
- - - "~>"
44
+ - - ">="
46
45
  - !ruby/object:Gem::Version
47
46
  version: '2.1'
48
47
  - !ruby/object:Gem::Dependency
@@ -73,9 +72,9 @@ dependencies:
73
72
  - - "~>"
74
73
  - !ruby/object:Gem::Version
75
74
  version: '3.11'
76
- description: " Easier nested form in Rails with capabilities to add, remove, reorder
77
- or limit nested items. Works with standard Rails form builder, Formtastic or SimpleForm,
78
- and with or without jQuery. "
75
+ description: " Rails nested form made easy, with standard Rails form builder, Formtastic
76
+ or SimpleForm. Unobtrusive and zero-dependencies JavaScript included, with optional
77
+ integration with jQuery and Stimulus. "
79
78
  email:
80
79
  - gael-ian@notus.sh
81
80
  - nathan@dixis.com
@@ -123,7 +122,6 @@ metadata:
123
122
  homepage_uri: https://github.com/notus-sh/cocooned
124
123
  source_code_uri: https://github.com/notus-sh/cocooned
125
124
  funding_uri: https://opencollective.com/notus-sh
126
- post_install_message:
127
125
  rdoc_options: []
128
126
  require_paths:
129
127
  - lib
@@ -131,15 +129,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
131
129
  requirements:
132
130
  - - ">="
133
131
  - !ruby/object:Gem::Version
134
- version: '2.7'
132
+ version: '3.1'
135
133
  required_rubygems_version: !ruby/object:Gem::Requirement
136
134
  requirements:
137
135
  - - ">="
138
136
  - !ruby/object:Gem::Version
139
137
  version: '0'
140
138
  requirements: []
141
- rubygems_version: 3.5.3
142
- signing_key:
139
+ rubygems_version: 4.0.3
143
140
  specification_version: 4
144
141
  summary: Form builder agnostic handling of Rails nested forms
145
142
  test_files: []