ultimate_turbo_modal 2.2.2 → 3.0.0.beta.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 +4 -4
- data/.rubocop.yml +1 -1
- data/.ruby-version +1 -1
- data/.standard.yml +2 -2
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +27 -24
- data/LLM/UPGRADE-TO-VERSION-3.md +38 -0
- data/README.md +105 -48
- data/UPGRADING.md +53 -14
- data/VERSION +1 -1
- data/docs/body-appended-widgets.md +124 -0
- data/lib/generators/ultimate_turbo_modal/base.rb +17 -12
- data/lib/generators/ultimate_turbo_modal/install_generator.rb +31 -16
- data/lib/generators/ultimate_turbo_modal/templates/flavors/custom.rb +33 -40
- data/lib/generators/ultimate_turbo_modal/templates/flavors/tailwind.rb +101 -41
- data/lib/generators/ultimate_turbo_modal/templates/flavors/vanilla.rb +31 -42
- data/lib/generators/ultimate_turbo_modal/templates/ultimate_turbo_modal.rb +21 -6
- data/lib/generators/ultimate_turbo_modal/update_generator.rb +20 -22
- data/lib/phlex/deferred_render_with_main_content.rb +1 -1
- data/lib/tasks/ultimate_turbo_modal.rake +8 -0
- data/lib/ultimate_turbo_modal/base.rb +212 -104
- data/lib/ultimate_turbo_modal/configuration.rb +70 -34
- data/lib/ultimate_turbo_modal/helpers/view_helper.rb +7 -0
- data/mise.toml +3 -0
- data/script/build_and_release.sh +4 -4
- metadata +17 -78
- data/.claude/TM_COMMANDS_GUIDE.md +0 -0
- data/.claude/commands/tm/add-dependency/add-dependency.md +0 -0
- data/.claude/commands/tm/add-subtask/add-subtask.md +0 -0
- data/.claude/commands/tm/add-subtask/convert-task-to-subtask.md +0 -0
- data/.claude/commands/tm/add-task/add-task.md +0 -0
- data/.claude/commands/tm/analyze-complexity/analyze-complexity.md +0 -0
- data/.claude/commands/tm/clear-subtasks/clear-all-subtasks.md +0 -0
- data/.claude/commands/tm/clear-subtasks/clear-subtasks.md +0 -0
- data/.claude/commands/tm/complexity-report/complexity-report.md +0 -0
- data/.claude/commands/tm/expand/expand-all-tasks.md +0 -0
- data/.claude/commands/tm/expand/expand-task.md +0 -0
- data/.claude/commands/tm/fix-dependencies/fix-dependencies.md +0 -0
- data/.claude/commands/tm/generate/generate-tasks.md +0 -0
- data/.claude/commands/tm/help.md +0 -0
- data/.claude/commands/tm/init/init-project-quick.md +0 -0
- data/.claude/commands/tm/init/init-project.md +0 -0
- data/.claude/commands/tm/learn.md +0 -0
- data/.claude/commands/tm/list/list-tasks-by-status.md +0 -0
- data/.claude/commands/tm/list/list-tasks-with-subtasks.md +0 -0
- data/.claude/commands/tm/list/list-tasks.md +0 -0
- data/.claude/commands/tm/models/setup-models.md +0 -0
- data/.claude/commands/tm/models/view-models.md +0 -0
- data/.claude/commands/tm/next/next-task.md +0 -0
- data/.claude/commands/tm/parse-prd/parse-prd-with-research.md +0 -0
- data/.claude/commands/tm/parse-prd/parse-prd.md +0 -0
- data/.claude/commands/tm/remove-dependency/remove-dependency.md +0 -0
- data/.claude/commands/tm/remove-subtask/remove-subtask.md +0 -0
- data/.claude/commands/tm/remove-task/remove-task.md +0 -0
- data/.claude/commands/tm/set-status/to-cancelled.md +0 -0
- data/.claude/commands/tm/set-status/to-deferred.md +0 -0
- data/.claude/commands/tm/set-status/to-done.md +0 -0
- data/.claude/commands/tm/set-status/to-in-progress.md +0 -0
- data/.claude/commands/tm/set-status/to-pending.md +0 -0
- data/.claude/commands/tm/set-status/to-review.md +0 -0
- data/.claude/commands/tm/setup/install-taskmaster.md +0 -0
- data/.claude/commands/tm/setup/quick-install-taskmaster.md +0 -0
- data/.claude/commands/tm/show/show-task.md +0 -0
- data/.claude/commands/tm/status/project-status.md +0 -0
- data/.claude/commands/tm/sync-readme/sync-readme.md +0 -0
- data/.claude/commands/tm/tm-main.md +0 -0
- data/.claude/commands/tm/update/update-single-task.md +0 -0
- data/.claude/commands/tm/update/update-task.md +0 -0
- data/.claude/commands/tm/update/update-tasks-from-id.md +0 -0
- data/.claude/commands/tm/utils/analyze-project.md +0 -0
- data/.claude/commands/tm/validate-dependencies/validate-dependencies.md +0 -0
- data/.claude/commands/tm/workflows/auto-implement-tasks.md +0 -0
- data/.claude/commands/tm/workflows/command-pipeline.md +0 -0
- data/.claude/commands/tm/workflows/smart-workflow.md +0 -0
- data/.taskmaster/CLAUDE.md +0 -0
- data/.taskmaster/config.json +0 -37
- data/.taskmaster/templates/example_prd.txt +0 -0
- data/.tool-versions +0 -2
- data/CLAUDE.md +0 -268
- data/javascript/index.js +0 -46
- data/javascript/modal_controller.js +0 -243
- data/javascript/package.json +0 -51
- data/javascript/rollup.config.js +0 -35
- data/javascript/scripts/release-npm.sh +0 -35
- data/javascript/scripts/update-version.js +0 -21
- data/javascript/styles/vanilla.css +0 -265
- data/javascript/yarn.lock +0 -638
- data/lib/generators/ultimate_turbo_modal/templates/flavors/tailwind3.rb +0 -48
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e555f71c3c4e4ca7af4849d73ab342a1641da8cc3e102d5dafb1048d45b7462
|
|
4
|
+
data.tar.gz: 8783d6e1d459a6bd02443bb1ff4bbf6b4c1581efe31b9483e7feb9cbb842e99d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 650f91e9d8eaa38eb5b92bf8a2a090ae7f470ecdb7aca6ae0017adca6967bf973a36b50e4b6acec3aacac0b8fb46dd5022ac453deda60fccecfb868a57234bae
|
|
7
|
+
data.tar.gz: b93f9c24991c4b8940b4dd13e107de4b4839a5ba7c652d859824effaca64c1135eaab7f63d967eaff81b34fe8e115a61f2265acd59ca9c68e89c5c88935e21ac
|
data/.rubocop.yml
CHANGED
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ruby-
|
|
1
|
+
ruby-4.0.1
|
data/.standard.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [3.0.0] - Unreleased
|
|
2
|
+
|
|
3
|
+
- **BREAKING**: Replaced div-based modal with native HTML `<dialog>` element.
|
|
4
|
+
- **BREAKING**: If you customized the look and feel of a flavor, you'll need to reapply your customizations since the underlying markup has changed.
|
|
5
|
+
- **BREAKING**: Removed Tailwind v3 flavor. Use the `tailwind` flavor (Tailwind v4+) or `custom` to define your own classes.
|
|
6
|
+
- **BREAKING**: Configuration options are now split between `config.modal` and `config.drawer` blocks instead of flat on the config object. See UPGRADING.md for migration details.
|
|
7
|
+
- **BREAKING**: Rails 8+ now required.
|
|
8
|
+
- Added support for slide-out drawers with separate default configuration.
|
|
9
|
+
- Smooth redirect behavior: same-page redirects morph content behind modal/drawer before closing; different-page redirects close modal/drawer with animation before navigating.
|
|
10
|
+
- Removed `el-transition` and `focus-trap` npm dependencies.
|
|
11
|
+
- ... plus a million tweaks, optimizations, refactors, etc.
|
|
12
|
+
|
|
1
13
|
## [2.2.2] - 2026-03-12
|
|
2
14
|
|
|
3
15
|
- Added `close` function on Stimulus Controller. Thanks @bendangelo
|
data/Gemfile.lock
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
ultimate_turbo_modal (
|
|
5
|
-
actionpack
|
|
6
|
-
activesupport
|
|
7
|
-
phlex-rails
|
|
8
|
-
railties
|
|
4
|
+
ultimate_turbo_modal (3.0.0.beta.2)
|
|
5
|
+
actionpack (>= 8.0)
|
|
6
|
+
activesupport (>= 8.0)
|
|
7
|
+
phlex-rails (>= 2.0)
|
|
8
|
+
railties (>= 8.0)
|
|
9
9
|
stimulus-rails
|
|
10
10
|
turbo-rails
|
|
11
11
|
|
|
@@ -60,18 +60,20 @@ GEM
|
|
|
60
60
|
pp (>= 0.6.0)
|
|
61
61
|
rdoc (>= 4.0.0)
|
|
62
62
|
reline (>= 0.4.2)
|
|
63
|
-
json (2.
|
|
63
|
+
json (2.19.2)
|
|
64
64
|
language_server-protocol (3.17.0.5)
|
|
65
65
|
lint_roller (1.1.0)
|
|
66
66
|
logger (1.7.0)
|
|
67
67
|
loofah (2.24.1)
|
|
68
68
|
crass (~> 1.0.2)
|
|
69
69
|
nokogiri (>= 1.12.0)
|
|
70
|
-
minitest (
|
|
71
|
-
|
|
70
|
+
minitest (6.0.2)
|
|
71
|
+
drb (~> 2.0)
|
|
72
|
+
prism (~> 1.5)
|
|
73
|
+
nokogiri (1.19.2-arm64-darwin)
|
|
72
74
|
racc (~> 1.4)
|
|
73
75
|
parallel (1.27.0)
|
|
74
|
-
parser (3.3.
|
|
76
|
+
parser (3.3.10.2)
|
|
75
77
|
ast (~> 2.4.1)
|
|
76
78
|
racc
|
|
77
79
|
phlex (2.3.1)
|
|
@@ -83,7 +85,7 @@ GEM
|
|
|
83
85
|
pp (0.6.2)
|
|
84
86
|
prettyprint
|
|
85
87
|
prettyprint (0.2.0)
|
|
86
|
-
prism (1.
|
|
88
|
+
prism (1.9.0)
|
|
87
89
|
psych (5.2.6)
|
|
88
90
|
date
|
|
89
91
|
stringio
|
|
@@ -116,10 +118,10 @@ GEM
|
|
|
116
118
|
rdoc (6.14.2)
|
|
117
119
|
erb
|
|
118
120
|
psych (>= 4.0.0)
|
|
119
|
-
regexp_parser (2.11.
|
|
121
|
+
regexp_parser (2.11.3)
|
|
120
122
|
reline (0.6.2)
|
|
121
123
|
io-console (~> 0.5)
|
|
122
|
-
rubocop (1.
|
|
124
|
+
rubocop (1.84.2)
|
|
123
125
|
json (~> 2.3)
|
|
124
126
|
language_server-protocol (~> 3.17.0.2)
|
|
125
127
|
lint_roller (~> 1.1.0)
|
|
@@ -127,16 +129,16 @@ GEM
|
|
|
127
129
|
parser (>= 3.3.0.2)
|
|
128
130
|
rainbow (>= 2.2.2, < 4.0)
|
|
129
131
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
130
|
-
rubocop-ast (>= 1.
|
|
132
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
131
133
|
ruby-progressbar (~> 1.7)
|
|
132
134
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
133
|
-
rubocop-ast (1.
|
|
135
|
+
rubocop-ast (1.49.1)
|
|
134
136
|
parser (>= 3.3.7.2)
|
|
135
|
-
prism (~> 1.
|
|
136
|
-
rubocop-performance (1.
|
|
137
|
+
prism (~> 1.7)
|
|
138
|
+
rubocop-performance (1.26.1)
|
|
137
139
|
lint_roller (~> 1.1)
|
|
138
140
|
rubocop (>= 1.75.0, < 2.0)
|
|
139
|
-
rubocop-ast (>= 1.
|
|
141
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
140
142
|
rubocop-rails (2.31.0)
|
|
141
143
|
activesupport (>= 4.2.0)
|
|
142
144
|
lint_roller (~> 1.1)
|
|
@@ -145,18 +147,18 @@ GEM
|
|
|
145
147
|
rubocop-ast (>= 1.38.0, < 2.0)
|
|
146
148
|
ruby-progressbar (1.13.0)
|
|
147
149
|
securerandom (0.4.1)
|
|
148
|
-
standard (1.
|
|
150
|
+
standard (1.54.0)
|
|
149
151
|
language_server-protocol (~> 3.17.0.2)
|
|
150
152
|
lint_roller (~> 1.0)
|
|
151
|
-
rubocop (~> 1.
|
|
153
|
+
rubocop (~> 1.84.0)
|
|
152
154
|
standard-custom (~> 1.0.0)
|
|
153
155
|
standard-performance (~> 1.8)
|
|
154
156
|
standard-custom (1.0.2)
|
|
155
157
|
lint_roller (~> 1.0)
|
|
156
158
|
rubocop (~> 1.50)
|
|
157
|
-
standard-performance (1.
|
|
159
|
+
standard-performance (1.9.0)
|
|
158
160
|
lint_roller (~> 1.1)
|
|
159
|
-
rubocop-performance (~> 1.
|
|
161
|
+
rubocop-performance (~> 1.26.0)
|
|
160
162
|
standard-rails (1.4.0)
|
|
161
163
|
lint_roller (~> 1.0)
|
|
162
164
|
rubocop-rails (~> 2.31.0)
|
|
@@ -169,9 +171,9 @@ GEM
|
|
|
169
171
|
railties (>= 7.1.0)
|
|
170
172
|
tzinfo (2.0.6)
|
|
171
173
|
concurrent-ruby (~> 1.0)
|
|
172
|
-
unicode-display_width (3.
|
|
173
|
-
unicode-emoji (~> 4.
|
|
174
|
-
unicode-emoji (4.0
|
|
174
|
+
unicode-display_width (3.2.0)
|
|
175
|
+
unicode-emoji (~> 4.1)
|
|
176
|
+
unicode-emoji (4.2.0)
|
|
175
177
|
uri (1.0.3)
|
|
176
178
|
useragent (0.16.11)
|
|
177
179
|
zeitwerk (2.7.3)
|
|
@@ -180,6 +182,7 @@ PLATFORMS
|
|
|
180
182
|
arm64-darwin-22
|
|
181
183
|
arm64-darwin-23
|
|
182
184
|
arm64-darwin-24
|
|
185
|
+
arm64-darwin-25
|
|
183
186
|
|
|
184
187
|
DEPENDENCIES
|
|
185
188
|
rake (~> 13.0)
|
|
@@ -0,0 +1,38 @@
|
|
|
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.
|
data/README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# The Ultimate Turbo Modal for Rails (UTMR)
|
|
2
2
|
|
|
3
|
-
There are MANY Turbo/Hotwire/Stimulus modal dialog implementations out there
|
|
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
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.
|
|
6
6
|
|
|
7
|
-
Under the hood, it uses [Stimulus](https://stimulus.hotwired.dev), [Turbo](https://turbo.hotwired.dev/), [
|
|
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
|
+
|
|
9
|
+
It ships in two flavors: Tailwind (v4+) and vanilla CSS. It is easy to create your own flavor to suit your needs.
|
|
8
10
|
|
|
9
|
-
It currently ships in a three flavors: Tailwind v3, Tailwind v4 and regular, vanilla CSS. It is easy to create your own variant to suit your needs.
|
|
10
11
|
|
|
11
12
|
## Installation
|
|
12
13
|
|
|
@@ -15,6 +16,7 @@ $ bundle add ultimate_turbo_modal
|
|
|
15
16
|
$ bundle exec rails g ultimate_turbo_modal:install
|
|
16
17
|
```
|
|
17
18
|
|
|
19
|
+
|
|
18
20
|
## Usage
|
|
19
21
|
|
|
20
22
|
1. Wrap your view inside a `modal` block as follow:
|
|
@@ -35,6 +37,8 @@ Clicking on the link will automatically open the content of the view inside a mo
|
|
|
35
37
|
|
|
36
38
|
This is really all you should need to do for most use cases.
|
|
37
39
|
|
|
40
|
+
**Please note:** The generator automatically adds `<turbo-frame id="modal"></turbo-frame>` to your application layout. If you need to open modals or drawers in another layout, please add this HTML snippet manually.
|
|
41
|
+
|
|
38
42
|
### Setting Title and Footer
|
|
39
43
|
|
|
40
44
|
You can set a custom title and footer by passing a block. For example:
|
|
@@ -76,15 +80,51 @@ If you need to do something a little bit more advanced when the view is shown ou
|
|
|
76
80
|
|
|
77
81
|
## Options
|
|
78
82
|
|
|
79
|
-
Do not get overwhelmed with all the options. The defaults are sensible.
|
|
83
|
+
Do not get overwhelmed with all the options. The defaults are sensible. You can change the defaults with an initializer:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
# config/initializers/ultimate_turbo_modal.rb
|
|
87
|
+
|
|
88
|
+
UltimateTurboModal.configure do |config|
|
|
89
|
+
config.flavor = :tailwind
|
|
90
|
+
config.allowed_click_outside_selector = []
|
|
91
|
+
|
|
92
|
+
config.modal do |m|
|
|
93
|
+
m.advance = false
|
|
94
|
+
m.close_button = true
|
|
95
|
+
m.header = true
|
|
96
|
+
m.header_divider = true
|
|
97
|
+
m.footer_divider = true
|
|
98
|
+
m.padding = true
|
|
99
|
+
m.overlay = true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
config.drawer do |d|
|
|
103
|
+
d.position = :right
|
|
104
|
+
d.close_button = true
|
|
105
|
+
d.header = true
|
|
106
|
+
d.header_divider = false
|
|
107
|
+
d.footer_divider = true
|
|
108
|
+
d.padding = true
|
|
109
|
+
d.overlay = true
|
|
110
|
+
d.size = :md
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Per-instance options passed to `modal()` or `drawer()` override the defaults.
|
|
80
116
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
|
117
|
+
### Modal Options
|
|
118
|
+
|
|
119
|
+
| Name | Default | Description |
|
|
120
|
+
|------|---------|-------------|
|
|
121
|
+
| `advance` | `false` | When opening the modal, the URL in the URL bar will change to the URL of the view being shown in the modal. The Back button dismisses the modal and navigates back. If a URL is specified as a string (e.g. `advance: "/other-path"`), the browser history will advance, and the URL shown in the URL bar will be replaced with the value specified. |
|
|
84
122
|
| `close_button` | `true` | Shows or hide a close button (X) at the top right of the modal. |
|
|
85
123
|
| `header` | `true` | Whether to display a modal header. |
|
|
86
124
|
| `header_divider` | `true` | Whether to display a divider below the header. |
|
|
125
|
+
| `footer_divider` | `true` | Whether to display a divider above the footer. |
|
|
87
126
|
| `padding` | `true` | Adds padding inside the modal. |
|
|
127
|
+
| `overlay` | `true` | Whether to show a backdrop overlay. |
|
|
88
128
|
| `title` | `nil` | Title to display in the modal header. Alternatively, you can set the title with a block. |
|
|
89
129
|
|
|
90
130
|
### Example usage with options
|
|
@@ -101,9 +141,62 @@ Do not get overwhelmed with all the options. The defaults are sensible.
|
|
|
101
141
|
<% end %>
|
|
102
142
|
```
|
|
103
143
|
|
|
144
|
+
## Drawers
|
|
145
|
+
|
|
146
|
+
UTMR includes built-in drawer (slide-out panel) support. Drawers share the same `<dialog>` element and Stimulus controller as modals — no additional JavaScript required.
|
|
147
|
+
|
|
148
|
+
### Basic Usage
|
|
149
|
+
|
|
150
|
+
Use the `drawer` helper instead of `modal`:
|
|
151
|
+
|
|
152
|
+
```erb
|
|
153
|
+
<%= drawer do %>
|
|
154
|
+
Drawer content here!
|
|
155
|
+
<% end %>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Link to it the same way as a modal:
|
|
159
|
+
|
|
160
|
+
```erb
|
|
161
|
+
<%= link_to "Open Drawer", "/settings", data: { turbo_frame: "modal" } %>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Drawer Options
|
|
165
|
+
|
|
166
|
+
| Name | Default | Description |
|
|
167
|
+
|------|---------|-------------|
|
|
168
|
+
| `position` | `:right` | Which edge the drawer slides from. `:right` or `:left`. |
|
|
169
|
+
| `size` | `:md` | Width of the drawer. One of `:xs`, `:sm`, `:md`, `:lg`, `:xl`, `:"2xl"`, `:full`, or a CSS string (e.g. `"500px"`). |
|
|
170
|
+
| `overlay` | `true` | Whether to show a backdrop overlay behind the drawer. |
|
|
171
|
+
| `close_button` | `true` | Shows or hide a close button (X). |
|
|
172
|
+
| `header` | `true` | Whether to display a header. |
|
|
173
|
+
| `header_divider` | `false` | Whether to display a divider below the header. |
|
|
174
|
+
| `footer_divider` | `true` | Whether to display a divider above the footer. |
|
|
175
|
+
| `padding` | `true` | Adds padding inside the drawer. |
|
|
176
|
+
| `title` | `nil` | Title to display in the drawer header. |
|
|
177
|
+
|
|
178
|
+
```erb
|
|
179
|
+
<%= drawer(position: :left, size: :lg, overlay: false, title: "Settings") do %>
|
|
180
|
+
<p>Drawer content</p>
|
|
181
|
+
<% end %>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Drawer Size Reference
|
|
185
|
+
|
|
186
|
+
| Size | Max Width |
|
|
187
|
+
|------|-----------|
|
|
188
|
+
| `:sm` | 24rem (384px) |
|
|
189
|
+
| `:md` | 28rem (448px) |
|
|
190
|
+
| `:lg` | 42rem (672px) |
|
|
191
|
+
| `:xl` | 56rem (896px) |
|
|
192
|
+
| `:full` | Full viewport width minus a small gutter |
|
|
193
|
+
| CSS string | Custom value, e.g. `"500px"` or `"50vw"` |
|
|
194
|
+
|
|
195
|
+
|
|
104
196
|
## Features and capabilities
|
|
105
197
|
|
|
106
198
|
- Extremely easy to use
|
|
199
|
+
- Built-in drawer (slide-out panel) support with left/right positioning and configurable sizes
|
|
107
200
|
- Fully responsive
|
|
108
201
|
- Does not break if a user navigates directly to a page that is usually shown in a modal
|
|
109
202
|
- Opening a modal in a new browser tab (ie: right click) gracefully degrades without having to code a modal and non-modal version of the same page
|
|
@@ -112,14 +205,14 @@ Do not get overwhelmed with all the options. The defaults are sensible.
|
|
|
112
205
|
- Seamless support for multi-page navigation within the modal
|
|
113
206
|
- Seamless support for forms with validations
|
|
114
207
|
- Seamless support for Rails flash messages
|
|
115
|
-
- Enter/leave animation (fade in/out)
|
|
116
208
|
- Support for long, scrollable modals
|
|
117
209
|
- Properly locks the background page when scrolling a long modal
|
|
118
210
|
- Click outside the modal to dismiss
|
|
119
|
-
- Option to whitelist CSS selectors that won't dismiss the modal when clicked outside the modal (
|
|
211
|
+
- Option to whitelist CSS selectors that won't dismiss the modal when clicked outside the modal (see [body-appended widgets guide](docs/body-appended-widgets.md) for datepickers and similar popups)
|
|
120
212
|
- Keyboard control; ESC to dismiss
|
|
121
213
|
- Automatic (or not) close button
|
|
122
|
-
-
|
|
214
|
+
- Native focus trapping via the `<dialog>` element for improved accessibility (Tab and Shift+Tab cycle through focusable elements within the modal only)
|
|
215
|
+
- 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
|
|
123
216
|
|
|
124
217
|
|
|
125
218
|
## Demo Video
|
|
@@ -134,15 +227,6 @@ The repository includes a demo application in the `demo-app` directory that show
|
|
|
134
227
|
# Navigate to the demo app directory
|
|
135
228
|
cd demo-app
|
|
136
229
|
|
|
137
|
-
# Install Ruby dependencies
|
|
138
|
-
bundle install
|
|
139
|
-
|
|
140
|
-
# Create and setup the database
|
|
141
|
-
bin/rails db:create db:migrate db:seed
|
|
142
|
-
|
|
143
|
-
# Install JavaScript dependencies
|
|
144
|
-
yarn install
|
|
145
|
-
|
|
146
230
|
# Start the development server
|
|
147
231
|
bin/dev
|
|
148
232
|
|
|
@@ -150,38 +234,11 @@ bin/dev
|
|
|
150
234
|
open http://localhost:3000
|
|
151
235
|
```
|
|
152
236
|
|
|
153
|
-
The demo app provides examples of:
|
|
154
|
-
- Basic modal usage
|
|
155
|
-
- Different modal configurations
|
|
156
|
-
- Custom styling options
|
|
157
|
-
- Various trigger methods
|
|
158
|
-
- Advanced features like scrollable content and custom footers
|
|
159
|
-
|
|
160
|
-
## Updating between minor versions
|
|
161
|
-
|
|
162
|
-
To upgrade within the same major version (for example 2.1 → 2.2):
|
|
163
|
-
|
|
164
|
-
1. Change the UTMR gem version in your `Gemfile`:
|
|
165
|
-
|
|
166
|
-
```ruby
|
|
167
|
-
gem "ultimate_turbo_modal", "~> 2.2"
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
2. Install updated dependencies:
|
|
171
|
-
|
|
172
|
-
```sh
|
|
173
|
-
bundle install
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
3. Run the update generator:
|
|
177
237
|
|
|
178
|
-
|
|
179
|
-
bundle exec rails g ultimate_turbo_modal:update
|
|
180
|
-
```
|
|
238
|
+
## Upgrading
|
|
181
239
|
|
|
182
|
-
|
|
240
|
+
Please see the [Upgrading Guide](UPGRADING.md) for detailed instructions on upgrading between versions.
|
|
183
241
|
|
|
184
|
-
Please see the [Upgrading Guide](UPGRADING.md) for detailed instructions on how to upgrade from version 1.x.
|
|
185
242
|
|
|
186
243
|
## Thanks
|
|
187
244
|
|
data/UPGRADING.md
CHANGED
|
@@ -1,8 +1,53 @@
|
|
|
1
|
-
# Upgrading
|
|
1
|
+
# Upgrading
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Updating between minor versions
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
To upgrade within the same major version (for example 3.0 → 3.1):
|
|
6
|
+
|
|
7
|
+
1. Change the UTMR gem version in your `Gemfile`:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "ultimate_turbo_modal", "~> 3.0"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. Install updated dependencies:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
3. Run the update generator:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
bundle exec rails g ultimate_turbo_modal:update
|
|
23
|
+
```
|
|
24
|
+
|
|
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
|
+
## Upgrading from 1.x to 2.x (LEGACY VERSIONS)
|
|
47
|
+
|
|
48
|
+
Version 2.0 of Ultimate Turbo Modal introduced some significant changes to simplify the setup and usage. Follow these steps to upgrade from a 1.x version.
|
|
49
|
+
|
|
50
|
+
### 1. Gem and Package Update
|
|
6
51
|
|
|
7
52
|
1. Update the gem in your `Gemfile`:
|
|
8
53
|
```ruby
|
|
@@ -14,24 +59,24 @@ Version 2.0 of Ultimate Turbo Modal introduces some significant changes to simpl
|
|
|
14
59
|
```
|
|
15
60
|
3. Run `bundle install` and `yarn install` (or `npm install`).
|
|
16
61
|
|
|
17
|
-
|
|
62
|
+
### 2. JavaScript Changes
|
|
18
63
|
|
|
19
|
-
The biggest change in v2
|
|
64
|
+
The biggest change in v2 was the removal of the `setupUltimateTurboModal` initializer. The modal controller now handles everything automatically.
|
|
20
65
|
|
|
21
|
-
|
|
66
|
+
#### Remove Initializer
|
|
22
67
|
|
|
23
68
|
- Remove the two `setupUltimateTurboModal`-related lines from `app/javascript/controllers/index.js`.
|
|
24
69
|
|
|
25
70
|
Your `index.js` should no longer import `setupUltimateTurboModal` or call it. The Stimulus controller will be automatically loaded.
|
|
26
71
|
|
|
27
|
-
|
|
72
|
+
#### Remove Idiomorph Tweaks (if you used them)
|
|
28
73
|
|
|
29
74
|
If you were using the optional Idiomorph tweaks for better morphing, you can remove them as this is now handled differently.
|
|
30
75
|
|
|
31
76
|
- Remove `<script src="https://unpkg.com/idiomorph"></script>` from your application layout.
|
|
32
77
|
- Remove the `turbo:before-frame-render` event listener from your `application.js`.
|
|
33
78
|
|
|
34
|
-
|
|
79
|
+
### 3. Tailwind CSS Changes
|
|
35
80
|
|
|
36
81
|
- Remove any `ultimate_turbo_modal` specific paths from your `tailwind.config.js`. The modal's classes are now self-contained and don't require scanning the gem's view files.
|
|
37
82
|
|
|
@@ -47,9 +92,3 @@ module.exports = {
|
|
|
47
92
|
]
|
|
48
93
|
}
|
|
49
94
|
```
|
|
50
|
-
|
|
51
|
-
## 4. Review Usage
|
|
52
|
-
|
|
53
|
-
Version 2.0 aims for backward compatibility in how you render modals from your Rails views and controllers. However, it's always a good idea to test your modals after upgrading to ensure they behave as expected.
|
|
54
|
-
|
|
55
|
-
That's it! You should now be running on version 2.0.
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3.0.0.beta.2
|
|
@@ -0,0 +1,124 @@
|
|
|
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.
|