ultimate_turbo_modal 3.0.0 → 3.0.2

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: e411c9c81c6a9d66e510bec9419320098ffe600585b40c7dfb2030be2a5850d9
4
- data.tar.gz: 6d96129a3bc0c1df07c42e3fc590a4fb9fcc33d3b52ebb07b36c0746f5337e04
3
+ metadata.gz: 4006bd50ba1693d0d012a648cf4a8d500082f9f58ba02c00d828233d3437b46a
4
+ data.tar.gz: e7377b811456a99d875a0d0450690d63c4e1dec41e1e8e21dfe5110210e529a5
5
5
  SHA512:
6
- metadata.gz: 4a1946c9a3238294a9c4ea52a1459ea9e6e220eee55e3e4ed3717e1bae5840922c52c6d9b01dc42d8b08450b0c36ee51090854cad36057d39e7d06f7943f5955
7
- data.tar.gz: f82213efb17b0a364e06d1e131684466e9b5f6e35f2cdf0166ecab10b191469e1b96fc035955e5fb612259e30d05f5bbc16cf515c646fbf1e65b1b8ca1d791c4
6
+ metadata.gz: 85529b5b0438d441e75bb50ecc58733d938a3e4486b8935273c57bc7f3a5bb82e99a263a92a5ccb7618ddfd860ad84be3cd1de7d9f3146d9eba07977dff7a6c0
7
+ data.tar.gz: d2d481a1a9713e6adec93a679c3f59ad285eb7acd6c866a01d45cf496fa82422ea0c84fe2c060439af41950276645d889f216404714ead27df185b890bcff743
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [3.0.2] - 2026-03-22
2
+
3
+ - Fixed npm package not being found on JSPM, which prevented `bin/importmap pin` from working ([#46](https://github.com/cmer/ultimate_turbo_modal/issues/46)).
4
+
5
+ ## [3.0.1] - 2026-03-21
6
+
7
+ - Fixed missing bottom padding on vanilla flavor drawer header when header divider is enabled.
8
+
1
9
  ## [3.0.0] - 2026-03-21
2
10
 
3
11
  Upgrading is easy! See [UPGRADING.md](UPGRADING.md)
data/Gemfile.lock CHANGED
@@ -1,12 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ultimate_turbo_modal (3.0.0)
4
+ ultimate_turbo_modal (3.0.2)
5
5
  actionpack (>= 8.0)
6
6
  activesupport (>= 8.0)
7
7
  phlex-rails (>= 2.0)
8
8
  railties (>= 8.0)
9
9
  stimulus-rails
10
+ tsort
10
11
  turbo-rails
11
12
 
12
13
  GEM
@@ -166,6 +167,7 @@ GEM
166
167
  railties (>= 6.0.0)
167
168
  stringio (3.1.7)
168
169
  thor (1.4.0)
170
+ tsort (0.2.0)
169
171
  turbo-rails (2.0.16)
170
172
  actionpack (>= 7.1.0)
171
173
  railties (>= 7.1.0)
data/README.md CHANGED
@@ -2,12 +2,22 @@
2
2
 
3
3
  There are MANY Turbo/Hotwire/Stimulus modal dialog implementations out there. However, as you may have learned, the majority fall short in different, often subtle ways. They generally cover the basics quite well, but do not check all the boxes for real-world use.
4
4
 
5
- UTMR aims to be the be-all and end-all of Turbo Modals. I believe it is the best (only?) full-featured implementation and checks all the boxes. It is feature-rich, yet extremely easy to use.
5
+ UTMR aims to be the be-all and end-all of Turbo Modals. I believe it is the best (only?) full-featured implementation and checks all the boxes. It is feature-rich, yet extremely easy to use. Its purpose is to make it as easy as possible to have polished Turbo-backed modals and drawers.
6
6
 
7
7
  Under the hood, it uses [Stimulus](https://stimulus.hotwired.dev), [Turbo](https://turbo.hotwired.dev/), the native HTML [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog) element, and [Idiomorph](https://github.com/bigskysoftware/idiomorph).
8
8
 
9
9
  It ships in two flavors: Tailwind (v4+) and vanilla CSS. It is easy to create your own flavor to suit your needs.
10
10
 
11
+ ## Screenshots & Demo Video
12
+
13
+ [![Demo Video](/screenshots/light-showcase-play.webp "Demo Video")](https://youtu.be/qXoeyxuyn7w)
14
+
15
+ | | |
16
+ |:-------------------------:|:-------------------------:|
17
+ | ![Light Modal Form](/screenshots/light-modal-form.webp "Light Modal Form") | ![Light Long Scrollable Modal](/screenshots/light-long-scrollable-modal.webp "Light Long Scrollable Modal") |
18
+ | ![Light Drawer with Footer](/screenshots/light-drawer-with-footer.webp "Light Drawer with Footer") | ![Dark Modal Form](/screenshots/dark-modal-form.webp "Dark Modal Form") |
19
+ | | |
20
+
11
21
 
12
22
  ## Installation
13
23
 
@@ -215,10 +225,6 @@ Link to it the same way as a modal:
215
225
  - Smooth redirects: form submissions that redirect back to the same page morph the content behind the modal before closing; redirects to a different page close the modal with animation first, then navigate
216
226
 
217
227
 
218
- ## Demo Video
219
-
220
- A video demo can be seen here: [https://youtu.be/qXoeyxuyn7w](https://youtu.be/qXoeyxuyn7w).
221
-
222
228
  ### Running the Demo Application
223
229
 
224
230
  The repository includes a demo application in the `demo-app` directory that showcases all the features of Ultimate Turbo Modal. To run it locally:
data/UPGRADING.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Upgrading
2
2
 
3
+ ## Upgrading from 2.x to 3.0
4
+
5
+ **Tell your favorite LLM agent to upgrade UTMR for you!** Use this prompt:
6
+
7
+ ```
8
+ Retrieve and follow the instructions at https://raw.githubusercontent.com/cmer/ultimate_turbo_modal/main/LLM/UPGRADE-TO-VERSION-3.md to upgrade this application's "Ultimate Turbo Modal" dependency from v2 to v3.
9
+ ```
10
+
11
+ v3.0 includes a few breaking changes. See [CHANGELOG.md](CHANGELOG.md) for a complete list of changes.
12
+
13
+ - **Native `<dialog>` element**: The modal now uses the native HTML `<dialog>` element instead of custom `<div>`-based markup. This provides native focus trapping and improved accessibility, removing the need for the `el-transition` and `focus-trap` JavaScript dependencies.
14
+ - **Simplified HTML structure**: The modal markup has been reduced from 6 nested containers to 3 (`dialog` + `inner` + `content`).
15
+ - **Tailwind v3 flavor removed**: Only Tailwind v4+ is supported via the `tailwind` flavor. Use `custom` if you need to define your own classes.
16
+ - **Custom flavor update required**: The flavor constants `DIV_MODAL_CONTAINER_CLASSES`, `DIV_OVERLAY_CLASSES`, `DIV_DIALOG_CLASSES`, and `TRANSITIONS` have been replaced by `DIALOG_CLASSES`. If you have a custom flavor, you must update it to use the new constants.
17
+ - **Configuration restructured**: Configuration options are now split between `config.modal` and `config.drawer` blocks instead of being flat on the config object. This allows modals and drawers to have different defaults.
18
+
19
+ ### Upgrading like a cavemen
20
+
21
+ If you're old school and prefer to upgrade manually, without an LLM, it's pretty easy. Just follow the instructions at [LLM/UPGRADE-TO-VERSION-3.md](LLM/UPGRADE-TO-VERSION-3.md).
22
+
23
+
3
24
  ## Updating between minor versions
4
25
 
5
26
  To upgrade within the same major version (for example 3.0 → 3.1):
@@ -22,26 +43,6 @@ To upgrade within the same major version (for example 3.0 → 3.1):
22
43
  bundle exec rails g ultimate_turbo_modal:update
23
44
  ```
24
45
 
25
- ## Upgrading from 2.x to 3.0
26
-
27
- **Tell your favorite LLM agent to upgrade UTMR for you!** Use this prompt:
28
-
29
- ```
30
- Retrieve and follow the instructions at https://raw.githubusercontent.com/cmer/ultimate_turbo_modal/main/LLM/UPGRADE-TO-VERSION-3.md to upgrade this application's "Ultimate Turbo Modal" dependency from v2 to v3.
31
- ```
32
-
33
- v3.0 includes a few breaking changes. See [CHANGELOG.md](CHANGELOG.md) for a complete list of changes.
34
-
35
- - **Native `<dialog>` element**: The modal now uses the native HTML `<dialog>` element instead of custom `<div>`-based markup. This provides native focus trapping and improved accessibility, removing the need for the `el-transition` and `focus-trap` JavaScript dependencies.
36
- - **Simplified HTML structure**: The modal markup has been reduced from 6 nested containers to 3 (`dialog` + `inner` + `content`).
37
- - **Tailwind v3 flavor removed**: Only Tailwind v4+ is supported via the `tailwind` flavor. Use `custom` if you need to define your own classes.
38
- - **Custom flavor update required**: The flavor constants `DIV_MODAL_CONTAINER_CLASSES`, `DIV_OVERLAY_CLASSES`, `DIV_DIALOG_CLASSES`, and `TRANSITIONS` have been replaced by `DIALOG_CLASSES`. If you have a custom flavor, you must update it to use the new constants.
39
- - **Configuration restructured**: Configuration options are now split between `config.modal` and `config.drawer` blocks instead of being flat on the config object. This allows modals and drawers to have different defaults.
40
-
41
- ### Upgrading like a cavemen
42
-
43
- If you're old school and prefer to upgrade manually, without an LLM, it's pretty easy. Just follow the instructions at [LLM/UPGRADE-TO-VERSION-3.md].
44
-
45
46
 
46
47
  ## Upgrading from 1.x to 2.x (LEGACY VERSIONS)
47
48
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.0.0
1
+ 3.0.2
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ultimate_turbo_modal
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carl Mercier
@@ -79,6 +79,20 @@ dependencies:
79
79
  - - ">="
80
80
  - !ruby/object:Gem::Version
81
81
  version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: tsort
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
82
96
  - !ruby/object:Gem::Dependency
83
97
  name: turbo-rails
84
98
  requirement: !ruby/object:Gem::Requirement
@@ -111,12 +125,10 @@ files:
111
125
  - Gemfile
112
126
  - Gemfile.lock
113
127
  - LICENSE.txt
114
- - LLM/UPGRADE-TO-VERSION-3.md
115
128
  - README.md
116
129
  - Rakefile
117
130
  - UPGRADING.md
118
131
  - VERSION
119
- - docs/body-appended-widgets.md
120
132
  - lib/generators/ultimate_turbo_modal/base.rb
121
133
  - lib/generators/ultimate_turbo_modal/install_generator.rb
122
134
  - lib/generators/ultimate_turbo_modal/templates/flavors/custom.rb
@@ -135,7 +147,6 @@ files:
135
147
  - lib/ultimate_turbo_modal/railtie.rb
136
148
  - lib/ultimate_turbo_modal/version.rb
137
149
  - mise.toml
138
- - script/build_and_release.sh
139
150
  - sig/ultimate_turbo_modal.rbs
140
151
  - yarn.lock
141
152
  homepage: https://github.com/cmer/ultimate_turbo_modal
@@ -1,38 +0,0 @@
1
- # Upgrading a Ruby on Rails application from Ultimate Turbo Modal (UTMR) version 2 to version 3.
2
-
3
- ## Minimum requirements
4
-
5
- - Ruby 3.2 and above.
6
- - Rails 8 and above.
7
- - If the application uses Tailwind CSS, version 4 is now required. Tailwind 3 is deprecated. (breaking change)
8
- - `ultimate_turbo_modal` should be present in the host application's Gemfile. Otherwise, it hasn't been installed yet. User should follow installation steps instead.
9
-
10
- ## Important notes
11
-
12
- - Before starting with the upgrade, run `bundle exec rails runner "puts UltimateTurboModal.flavor"` to detect which flavor the host application is using. `tailwind3` is now deprecated. If the host application is using `tailwind3`, inform the user that it is no longer supported, and that the host application should upgrade to TailwindCSS 4 first.
13
- - If the flavor is different from the built-in `tailwind` or `vanilla`, they should be informed that they will need to reapply their custom styling changes manually since the HTML markup for UTMR has changed significantly.
14
- - If the application is using the `tailwind` or `vanilla`, no manual styling changes are required. The upgrade will be seamless.
15
-
16
- ## Steps to follow
17
-
18
- 1. Change the version number for `ultimate_turbo_modal` in Gemfile to `~> 3.0`.
19
- 2. If the application uses a Javascript package manager (npm, yarn, bun), change the version number for `ultimate_turbo_modal` in package.json to `^3.0.0`. Otherwise, if the application uses importmaps, update the `pin "ultimate_turbo_modal"` entry in `config/importmap.rb` to reference version `^3.0.0` (or run `bin/importmap pin ultimate_turbo_modal@^3.0.0`).
20
- 3. Run `bundle install`.
21
- 4. Run `bundle exec rails generate ultimate_turbo_modal:update` to align the npm package version with the Rubygem, and to copy the flavor file over. Note: this copies the flavor file (e.g. `config/initializers/ultimate_turbo_modal_tailwind.rb`) which defines CSS classes. It does **not** modify the configuration initializer (`config/initializers/ultimate_turbo_modal.rb`) which contains the `UltimateTurboModal.configure` block — that file must be migrated manually in step 6.
22
- 5. Run `yarn install` or `npm install` or `bun install` if using a Javascript package manager.
23
- 6. Migrate the configuration block in `config/initializers/ultimate_turbo_modal.rb` (this is the file with the `UltimateTurboModal.configure` block, not the flavor file). If this file does not exist, skip this step — the user was using defaults and no migration is needed. The objective is to replicate the same behavior the user had in v2, using the new v3 syntax. To do this:
24
-
25
- a. Retrieve the v3 configuration template by running: `cat $(bundle info ultimate_turbo_modal --path)/lib/generators/ultimate_turbo_modal/templates/ultimate_turbo_modal.rb`. This template contains the new v3 format with all options commented out at their defaults. Note: the template contains a `FLAVOR` placeholder on the `config.flavor` line that will need to be replaced with the flavor symbol from the user's v2 file (e.g. `:tailwind`).
26
-
27
- b. Read the user's existing `config/initializers/ultimate_turbo_modal.rb` and note any options that differ from the v3 defaults. If all the user's v2 options match these defaults, you will not need to change any of the configurations in the next step.
28
-
29
- c. Use the template as the base for the new file. Uncomment and set only the options that differ from the v3 defaults. The mapping is:
30
- - `config.flavor` and `config.allowed_click_outside_selector` remain top-level (unchanged).
31
- - `config.advance` → `m.advance` inside `config.modal` block (advance only applies to modals, not drawers).
32
- - `config.close_button`, `config.header`, `config.header_divider`, `config.footer_divider`, `config.padding` → `m.<option>` inside `config.modal` block.
33
- - Leave the `config.drawer` block commented out (use defaults) since drawers is a new feature and was never previously configured.
34
- - Setting old-style flat options directly on `config` (e.g. `config.close_button = false`) will raise a `NoMethodError` in v3.
35
-
36
- d. Overwrite `config/initializers/ultimate_turbo_modal.rb` with the result. In addition to the modified configuration options, the output should include all commented-out configuration options from the template file. This makes it easier for the user to modify options in the future.
37
-
38
- 7. Instruct the user to restart their Rails application server for the changes to be picked up.
@@ -1,124 +0,0 @@
1
- # Body-Appended Widgets Inside Modals and Drawers
2
-
3
- ## The Problem
4
-
5
- Many JavaScript libraries (datepickers, dropdown menus, rich text editors, color pickers, etc.) append their popup/overlay elements directly to `<body>` rather than inside the triggering element. Examples include Flatpickr, Tippy.js, Select2, and Floating UI-based components.
6
-
7
- UTMR v3 uses the native `<dialog>` element opened with `showModal()`. When a dialog is open in this mode, the browser automatically marks **everything outside the dialog** as [inert](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inert) — meaning those elements cannot receive focus, clicks, or any other user interaction. This is a browser-level behavior and cannot be overridden with CSS or JavaScript.
8
-
9
- As a result, if a widget inside your modal triggers a popup that gets appended to `<body>` (outside the `<dialog>`), that popup will be inert and un-clickable.
10
-
11
- **Note:** The `allowed_click_outside_selector` configuration option does not solve this problem. It only prevents the modal from *dismissing* when clicking matching elements — it cannot make inert elements interactive again.
12
-
13
- ## Solutions
14
-
15
- ### Option 1: Configure the widget to render inside the dialog
16
-
17
- Most popup libraries accept a `container` or `appendTo` option. Set it to the dialog element or a container inside it.
18
-
19
- **Flatpickr example:**
20
-
21
- The UTMR demo app uses [stimulus-flatpickr](https://www.npmjs.com/package/stimulus-flatpickr) with a custom controller that moves the calendar inside the dialog after initialization. See the full implementation at [`demo-app/app/javascript/controllers/flatpickr_controller.js`](../demo-app/app/javascript/controllers/flatpickr_controller.js):
22
-
23
- ```javascript
24
- import Flatpickr from "stimulus-flatpickr"
25
-
26
- export default class extends Flatpickr {
27
- connect() {
28
- super.connect()
29
-
30
- const dialog = this.element.closest("dialog")
31
- if (dialog && this.fp?.calendarContainer) {
32
- dialog.appendChild(this.fp.calendarContainer)
33
- }
34
- }
35
- }
36
- ```
37
-
38
- If you're using Flatpickr directly (without the Stimulus wrapper), you can use the `appendTo` option instead:
39
-
40
- ```javascript
41
- import flatpickr from "flatpickr"
42
-
43
- const dialog = element.closest("dialog")
44
- flatpickr(element, {
45
- appendTo: dialog || undefined,
46
- })
47
- ```
48
-
49
- **Tippy.js example:**
50
-
51
- ```javascript
52
- tippy(element, {
53
- appendTo: element.closest("dialog") || document.body,
54
- })
55
- ```
56
-
57
- **Floating UI example:**
58
-
59
- When using Floating UI directly, render the floating element inside the dialog rather than appending it to `<body>`.
60
-
61
- ### Option 2: Move elements into the dialog with a MutationObserver
62
-
63
- If you cannot configure the library's append target, you can observe `<body>` for newly added elements and relocate them into the dialog automatically.
64
-
65
- ```javascript
66
- // Stimulus controller that relocates a widget's popups into the dialog
67
- import { Controller } from "@hotwired/stimulus"
68
-
69
- export default class extends Controller {
70
- static values = { selector: String } // e.g. ".flatpickr-calendar"
71
-
72
- connect() {
73
- this.dialog = this.element.closest("dialog")
74
- if (!this.dialog) return
75
-
76
- this.relocated = []
77
- this.observer = new MutationObserver((mutations) => {
78
- for (const mutation of mutations) {
79
- for (const node of mutation.addedNodes) {
80
- if (node.nodeType !== Node.ELEMENT_NODE) continue
81
- if (this.dialog.contains(node)) continue
82
- if (node.matches(this.selectorValue)) {
83
- this.#relocate(node)
84
- }
85
- }
86
- }
87
- })
88
-
89
- this.observer.observe(document.body, { childList: true })
90
- }
91
-
92
- disconnect() {
93
- this.observer?.disconnect()
94
- for (const { element, placeholder } of this.relocated) {
95
- placeholder.parentNode?.insertBefore(element, placeholder)
96
- placeholder.remove()
97
- }
98
- this.relocated = []
99
- }
100
-
101
- #relocate(element) {
102
- const placeholder = document.createComment("utmr-relocated")
103
- element.parentNode.insertBefore(placeholder, element)
104
- this.dialog.appendChild(element)
105
- this.relocated.push({ element, placeholder })
106
- }
107
- }
108
- ```
109
-
110
- Usage in your view:
111
-
112
- ```erb
113
- <div data-controller="relocate-widget" data-relocate-widget-selector-value=".flatpickr-calendar">
114
- <input data-controller="flatpickr" type="text">
115
- </div>
116
- ```
117
-
118
- ### Option 3: Use `<dialog>`-aware libraries
119
-
120
- Some newer UI libraries are aware of the `<dialog>` top layer and handle this correctly out of the box. When choosing new dependencies, check whether they support rendering inside `<dialog>` elements opened with `showModal()`.
121
-
122
- ## Background
123
-
124
- This is not specific to UTMR — it affects **any** use of the native `<dialog>` element with `showModal()`. The [HTML spec](https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-showmodal) requires that content outside the top-layer dialog be made inert. This is the same mechanism that provides native focus trapping and accessibility benefits.
@@ -1,61 +0,0 @@
1
- #!/bin/bash
2
- set -e
3
- cd $(dirname $0)/..
4
-
5
- if [ "$1" == "--help" ]; then
6
- echo "Usage: $0 [--skip-gem] [--skip-js]"
7
- echo ""
8
- echo "Options:"
9
- echo " --skip-gem Skip building and releasing the gem."
10
- echo " --skip-js Skip building and releasing the JavaScript."
11
- echo " --help Show this help message."
12
- exit 0
13
- fi
14
-
15
- # Check for uncommitted changes
16
- if ! git diff --quiet; then
17
- echo "There are uncommitted changes. Aborting."
18
- exit 1
19
- fi
20
-
21
-
22
- if [ "$1" != "--skip-gem" ]; then
23
- echo "Building and releasing gem..."
24
- bundle exec rake build
25
-
26
- # Update demo app with latest gem and JavaScript
27
- echo "Updating demo app with latest code..."
28
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
29
- DEMO_APP_DIR="$SCRIPT_DIR/../demo-app"
30
-
31
- # Build JavaScript package first
32
- echo "Building ultimate_turbo_modal JavaScript package..."
33
- (cd "$SCRIPT_DIR/../javascript" && npm run build)
34
-
35
- # Update demo app dependencies
36
- echo "Installing latest ultimate_turbo_modal in demo app..."
37
- (cd "$DEMO_APP_DIR" && bundle install)
38
- (cd "$DEMO_APP_DIR" && npm install)
39
-
40
- # Check if Gemfile.lock or demo-app files are git dirty
41
- if ! git diff --quiet Gemfile.lock demo-app/Gemfile.lock demo-app/package-lock.json; then
42
- echo "Lock files are dirty. Adding, committing, and pushing."
43
- git add Gemfile.lock demo-app/Gemfile.lock demo-app/package-lock.json
44
- git commit -m "Update lock files for demo app"
45
- fi
46
-
47
- bundle exec rake build
48
- bundle exec rake release
49
- else
50
- echo "Skipping gem build and release..."
51
- fi
52
-
53
- if [ "$1" != "--skip-js" ]; then
54
- echo "Building JavaScript..."
55
- cd javascript
56
- ./scripts/release-npm.sh
57
- else
58
- echo "Skipping JavaScript build..."
59
- fi
60
-
61
- echo "Done!"