ultimate_turbo_modal 2.1.2 → 2.2.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.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/.tool-versions +1 -1
  3. data/CHANGELOG.md +17 -0
  4. data/CLAUDE.md +188 -2
  5. data/Gemfile.lock +1 -1
  6. data/README.md +22 -0
  7. data/VERSION +1 -1
  8. data/javascript/index.js +12 -3
  9. data/javascript/modal_controller.js +16 -4
  10. data/javascript/package.json +2 -1
  11. data/javascript/styles/vanilla.css +86 -0
  12. data/lib/generators/ultimate_turbo_modal/base.rb +117 -0
  13. data/lib/generators/ultimate_turbo_modal/install_generator.rb +46 -95
  14. data/lib/generators/ultimate_turbo_modal/templates/flavors/custom.rb +29 -2
  15. data/lib/generators/ultimate_turbo_modal/templates/flavors/tailwind.rb +30 -3
  16. data/lib/generators/ultimate_turbo_modal/templates/flavors/tailwind3.rb +29 -2
  17. data/lib/generators/ultimate_turbo_modal/templates/flavors/vanilla.rb +31 -3
  18. data/lib/generators/ultimate_turbo_modal/update_generator.rb +109 -0
  19. data/lib/ultimate_turbo_modal/base.rb +35 -14
  20. data/lib/ultimate_turbo_modal.rb +2 -0
  21. data/script/build_and_release.sh +20 -7
  22. metadata +8 -99
  23. data/demo-app/.gitattributes +0 -7
  24. data/demo-app/.gitignore +0 -34
  25. data/demo-app/.ruby-version +0 -1
  26. data/demo-app/.tool-versions +0 -2
  27. data/demo-app/Gemfile +0 -41
  28. data/demo-app/Gemfile.lock +0 -248
  29. data/demo-app/Procfile.dev +0 -3
  30. data/demo-app/README.md +0 -15
  31. data/demo-app/Rakefile +0 -6
  32. data/demo-app/app/assets/builds/.keep +0 -0
  33. data/demo-app/app/assets/images/.keep +0 -0
  34. data/demo-app/app/assets/stylesheets/application.css +0 -42
  35. data/demo-app/app/controllers/application_controller.rb +0 -10
  36. data/demo-app/app/controllers/concerns/.keep +0 -0
  37. data/demo-app/app/controllers/concerns/set_flavor.rb +0 -10
  38. data/demo-app/app/controllers/hide_from_backends_controller.rb +0 -34
  39. data/demo-app/app/controllers/modal_controller.rb +0 -11
  40. data/demo-app/app/controllers/posts_controller.rb +0 -69
  41. data/demo-app/app/controllers/welcome_controller.rb +0 -2
  42. data/demo-app/app/helpers/application_helper.rb +0 -9
  43. data/demo-app/app/javascript/application.js +0 -4
  44. data/demo-app/app/javascript/controllers/application.js +0 -13
  45. data/demo-app/app/javascript/controllers/dark_mode_controller.js +0 -28
  46. data/demo-app/app/javascript/controllers/flash_controller.js +0 -9
  47. data/demo-app/app/javascript/controllers/hello_controller.js +0 -7
  48. data/demo-app/app/javascript/controllers/index.js +0 -15
  49. data/demo-app/app/models/application_record.rb +0 -3
  50. data/demo-app/app/models/concerns/.keep +0 -0
  51. data/demo-app/app/models/post.rb +0 -4
  52. data/demo-app/app/views/hide_from_backends/_notice.html.erb +0 -10
  53. data/demo-app/app/views/hide_from_backends/create.turbo_stream.erb +0 -2
  54. data/demo-app/app/views/hide_from_backends/new.html.erb +0 -30
  55. data/demo-app/app/views/layouts/application.html.erb +0 -40
  56. data/demo-app/app/views/modal/index.html.erb +0 -45
  57. data/demo-app/app/views/modal/show.html.erb +0 -39
  58. data/demo-app/app/views/posts/_form.html.erb +0 -36
  59. data/demo-app/app/views/posts/_post.html.erb +0 -9
  60. data/demo-app/app/views/posts/edit.html.erb +0 -9
  61. data/demo-app/app/views/posts/index.html.erb +0 -14
  62. data/demo-app/app/views/posts/new.html.erb +0 -3
  63. data/demo-app/app/views/posts/show.html.erb +0 -21
  64. data/demo-app/app/views/shared/_flash.html.erb +0 -13
  65. data/demo-app/app/views/welcome/index.html.erb +0 -19
  66. data/demo-app/bin/bundle +0 -109
  67. data/demo-app/bin/dev +0 -18
  68. data/demo-app/bin/rails +0 -4
  69. data/demo-app/bin/rake +0 -4
  70. data/demo-app/bin/setup +0 -34
  71. data/demo-app/config/application.rb +0 -43
  72. data/demo-app/config/boot.rb +0 -3
  73. data/demo-app/config/credentials.yml.enc +0 -1
  74. data/demo-app/config/database.yml +0 -25
  75. data/demo-app/config/environment.rb +0 -14
  76. data/demo-app/config/environments/development.rb +0 -51
  77. data/demo-app/config/environments/production.rb +0 -67
  78. data/demo-app/config/environments/test.rb +0 -42
  79. data/demo-app/config/initializers/assets.rb +0 -7
  80. data/demo-app/config/initializers/content_security_policy.rb +0 -25
  81. data/demo-app/config/initializers/filter_parameter_logging.rb +0 -8
  82. data/demo-app/config/initializers/inflections.rb +0 -14
  83. data/demo-app/config/initializers/new_framework_defaults_8_0.rb +0 -30
  84. data/demo-app/config/initializers/permissions_policy.rb +0 -11
  85. data/demo-app/config/initializers/ultimate_turbo_modal.rb +0 -12
  86. data/demo-app/config/initializers/ultimate_turbo_modal_tailwind.rb +0 -21
  87. data/demo-app/config/initializers/ultimate_turbo_modal_vanilla.rb +0 -21
  88. data/demo-app/config/locales/en.yml +0 -33
  89. data/demo-app/config/puma.rb +0 -41
  90. data/demo-app/config/routes.rb +0 -9
  91. data/demo-app/config.ru +0 -6
  92. data/demo-app/db/migrate/20230331002502_create_posts.rb +0 -10
  93. data/demo-app/db/migrate/20231031012703_add_post.rb +0 -9
  94. data/demo-app/db/migrate/20231128141054_add_publish_on_to_posts.rb +0 -5
  95. data/demo-app/db/schema.rb +0 -22
  96. data/demo-app/lib/assets/.keep +0 -0
  97. data/demo-app/lib/tasks/.keep +0 -0
  98. data/demo-app/log/.keep +0 -0
  99. data/demo-app/package.json +0 -28
  100. data/demo-app/postcss.config.js +0 -7
  101. data/demo-app/public/400.html +0 -114
  102. data/demo-app/public/404.html +0 -114
  103. data/demo-app/public/406-unsupported-browser.html +0 -114
  104. data/demo-app/public/422.html +0 -114
  105. data/demo-app/public/500.html +0 -114
  106. data/demo-app/public/apple-touch-icon-precomposed.png +0 -0
  107. data/demo-app/public/apple-touch-icon.png +0 -0
  108. data/demo-app/public/favicon.ico +0 -0
  109. data/demo-app/public/icon.png +0 -0
  110. data/demo-app/public/icon.svg +0 -3
  111. data/demo-app/public/img/bootstrap-logo-shadow.png +0 -0
  112. data/demo-app/public/img/unicat.jpg +0 -0
  113. data/demo-app/public/img/vanilla.png +0 -0
  114. data/demo-app/public/robots.txt +0 -1
  115. data/demo-app/tmp/.keep +0 -0
  116. data/demo-app/tmp/pids/.keep +0 -0
  117. data/demo-app/vendor/.keep +0 -0
  118. data/demo-app/yarn.lock +0 -1035
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
- require "pathname" # Needed for Pathname helper
4
+ require_relative "base"
5
5
 
6
6
  module UltimateTurboModal
7
7
  module Generators
8
- class InstallGenerator < Rails::Generators::Base
8
+ class InstallGenerator < UltimateTurboModal::Generators::Base
9
9
  source_root File.expand_path("templates", __dir__)
10
10
 
11
11
  desc "Installs UltimateTurboModal: copies initializer/flavor, sets up JS, registers Stimulus controller, adds Turbo Frame."
@@ -17,99 +17,61 @@ module UltimateTurboModal
17
17
 
18
18
  # Step 2: Setup Javascript Dependencies (Yarn/npm/Bun or Importmap)
19
19
  def setup_javascript_dependencies
20
- package_name = "ultimate_turbo_modal" # Or the actual npm package name if different
21
-
22
- say "\nAttempting to set up JavaScript dependencies...", :yellow
23
-
24
- if uses_importmaps?
25
- say "Detected Importmaps. Pinning #{package_name}...", :green
26
- run "bin/importmap pin #{package_name}"
27
-
28
- say "\n✅ Pinned '#{package_name}' via importmap.", :green
29
-
30
- elsif uses_javascript_bundler?
31
- say "Detected jsbundling-rails (Yarn/npm/Bun). Adding #{package_name} package...", :green
32
- if uses_yarn?
33
- run "yarn add #{package_name}"
34
- say "\n✅ Added '#{package_name}' using Yarn.", :green
35
- elsif uses_npm?
36
- run "npm install --save #{package_name}"
37
- say "\n✅ Added '#{package_name}' using npm.", :green
38
- elsif uses_bun?
39
- run "bun add #{package_name}"
40
- say "\n✅ Added '#{package_name}' using Bun.", :green
41
- else
42
- # Default or fallback: Try yarn, but provide instructions for others
43
- say "Attempting to add with Yarn. If you use npm or Bun, please add manually.", :yellow
44
- run "yarn add #{package_name}"
45
- say "\n✅ Attempted to add '#{package_name}' using Yarn.", :green
46
- say " If this failed or you use npm/bun, please run:", :yellow
47
- say " npm install --save #{package_name}", :cyan
48
- say " # or", :cyan
49
- say " bun add #{package_name}\n", :cyan
50
- end
51
- else
52
- # Fallback instructions if neither is clearly detected
53
- say "\nCould not automatically detect Importmaps or jsbundling-rails.", :yellow
54
- say "Please manually add the '#{package_name}' JavaScript package.", :yellow
55
- say "If using Importmaps: bin/importmap pin #{package_name}", :cyan
56
- say "If using Yarn: yarn add #{package_name}", :cyan
57
- say "If using npm: npm install --save #{package_name}", :cyan
58
- say "If using Bun: bun add #{package_name}", :cyan
59
- say "Then, import it in your app/javascript/application.js:", :yellow
60
- say "import '#{package_name}'\n", :cyan
61
- end
20
+ add_js_dependency
62
21
  end
63
22
 
64
23
  # Step 3: Register Stimulus Controller
65
24
  def setup_stimulus_controller
66
- stimulus_controller_path = rails_root_join("app", "javascript", "controllers", "index.js")
67
- controller_package = "ultimate_turbo_modal" # Package name where the controller is defined
68
- controller_name = "UltimateTurboModalController" # The exported controller class name
69
- stimulus_identifier = "modal" # The identifier for application.register
25
+ index_path = rails_root_join("app", "javascript", "controllers", "index.js")
26
+ application_path = rails_root_join("app", "javascript", "controllers", "application.js")
27
+ controller_name = "UltimateTurboModalController"
28
+ stimulus_identifier = "modal"
70
29
 
71
- import_line = "import { #{controller_name} } from \"#{controller_package}\"\n"
30
+ import_line = "import { #{controller_name} } from \"#{package_name}\"\n"
72
31
  register_line = "application.register(\"#{stimulus_identifier}\", #{controller_name})\n"
73
32
 
74
- say "\nAttempting to register Stimulus controller in #{stimulus_controller_path}...", :yellow
33
+ # Determine which file contains Application.start() it may be index.js or application.js
34
+ target_path, file_content = find_stimulus_target(index_path, application_path)
35
+
36
+ say "\nAttempting to register Stimulus controller...", :yellow
75
37
 
76
- unless File.exist?(stimulus_controller_path)
77
- say "❌ Stimulus controllers index file not found at #{stimulus_controller_path}.", :red
38
+ unless target_path
39
+ say "❌ Stimulus controllers file not found.", :red
78
40
  say " Please manually add the following lines to your Stimulus setup:", :yellow
79
41
  say " #{import_line.strip}", :cyan
80
42
  say " #{register_line.strip}\n", :cyan
81
- return # Exit this method if the file doesn't exist
43
+ return
82
44
  end
83
45
 
84
- # Read the file content to check if lines already exist
85
- file_content = File.read(stimulus_controller_path)
46
+ say " Target file: #{target_path}", :yellow
86
47
 
87
- # Insert the import statement after the last existing import or a common marker
88
- # Using a regex to find the Stimulus import is often reliable
89
- import_anchor = /import .* from "@hotwired\/stimulus"\n/
90
- if file_content.match?(import_anchor) && !file_content.include?(import_line)
91
- insert_into_file stimulus_controller_path, import_line, after: import_anchor
48
+ # Insert the import statement after an existing import from @hotwired/stimulus or ./application
49
+ import_anchor = /import .* from ["'](?:@hotwired\/stimulus|\.\/application)["']\n/
50
+ if file_content.include?(import_line)
51
+ say "⏩ Import statement already exists.", :blue
52
+ elsif file_content.match?(import_anchor)
53
+ insert_into_file target_path, import_line, after: import_anchor
92
54
  say "✅ Added import statement.", :green
93
- elsif !file_content.include?(import_line)
94
- # Fallback: insert at the beginning if Stimulus import wasn't found (less ideal)
95
- insert_into_file stimulus_controller_path, import_line, before: /import/
55
+ elsif file_content.match?(/import/)
56
+ insert_into_file target_path, import_line, before: /import/
96
57
  say "✅ Added import statement (fallback position).", :green
97
58
  else
98
- say "⏩ Import statement already exists.", :blue
59
+ prepend_to_file target_path, import_line
60
+ say "✅ Added import statement (prepended to file).", :green
99
61
  end
100
62
 
101
-
102
63
  # Insert the register statement after Application.start()
103
- register_anchor = /Application\.start$$$$\n/
104
- if file_content.match?(register_anchor) && !file_content.include?(register_line)
105
- insert_into_file stimulus_controller_path, register_line, after: register_anchor
106
- say "✅ Added controller registration.", :green
107
- elsif !file_content.include?(register_line)
108
- say " Could not find `Application.start()` line to insert registration after.", :red
109
- say " Please manually add this line after your Stimulus application starts:", :yellow
110
- say " #{register_line.strip}\n", :cyan
64
+ register_anchor = /Application\.start\(\)\n/
65
+ if file_content.include?(register_line)
66
+ say "⏩ Controller registration already exists.", :blue
67
+ elsif file_content.match?(register_anchor)
68
+ insert_into_file target_path, register_line, after: register_anchor
69
+ say " Added controller registration.", :green
111
70
  else
112
- say " Controller registration already exists.", :blue
71
+ say " Could not find `Application.start()` line in #{target_path}.", :red
72
+ say " Please manually add these lines to your Stimulus setup:", :yellow
73
+ say " #{import_line.strip}", :cyan
74
+ say " #{register_line.strip}\n", :cyan
113
75
  end
114
76
  end
115
77
 
@@ -196,28 +158,17 @@ module UltimateTurboModal
196
158
  end
197
159
  end
198
160
 
199
- def uses_importmaps?
200
- File.exist?(rails_root_join("config", "importmap.rb"))
201
- end
202
-
203
- def uses_javascript_bundler?
204
- File.exist?(rails_root_join("package.json"))
205
- end
206
-
207
- def uses_yarn?
208
- File.exist?(rails_root_join("yarn.lock"))
209
- end
210
-
211
- def uses_npm?
212
- File.exist?(rails_root_join("package-lock.json")) && !uses_yarn? && !uses_bun?
213
- end
214
-
215
- def uses_bun?
216
- File.exist?(rails_root_join("bun.lockb"))
217
- end
161
+ def find_stimulus_target(index_path, application_path)
162
+ [index_path, application_path].each do |path|
163
+ next unless File.exist?(path)
164
+ content = File.read(path)
165
+ return [path, content] if content.match?(/Application\.start\(\)/)
166
+ end
218
167
 
219
- def rails_root_join(*args)
220
- Pathname.new(destination_root).join(*args)
168
+ # Fall back to index.js even without Application.start()
169
+ if File.exist?(index_path)
170
+ [index_path, File.read(index_path)]
171
+ end
221
172
  end
222
173
  end
223
174
  end
@@ -4,9 +4,9 @@
4
4
  # TODO: define the classes for each HTML element.
5
5
  module UltimateTurboModal::Flavors
6
6
  class Custom < UltimateTurboModal::Base
7
- DIV_DIALOG_CLASSES = ""
7
+ DIV_MODAL_CONTAINER_CLASSES = ""
8
8
  DIV_OVERLAY_CLASSES = ""
9
- DIV_OUTER_CLASSES = ""
9
+ DIV_DIALOG_CLASSES = ""
10
10
  DIV_INNER_CLASSES = ""
11
11
  DIV_CONTENT_CLASSES = ""
12
12
  DIV_MAIN_CLASSES = ""
@@ -18,5 +18,32 @@ module UltimateTurboModal::Flavors
18
18
  BUTTON_CLOSE_SR_ONLY_CLASSES = ""
19
19
  CLOSE_BUTTON_TAG_CLASSES = ""
20
20
  ICON_CLOSE_CLASSES = ""
21
+
22
+ TRANSITIONS = {
23
+ overlay: {
24
+ enter: {
25
+ animation: "",
26
+ start: "",
27
+ end: ""
28
+ },
29
+ leave: {
30
+ animation: "",
31
+ start: "",
32
+ end: ""
33
+ }
34
+ },
35
+ dialog: {
36
+ enter: {
37
+ animation: "",
38
+ start: "",
39
+ end: ""
40
+ },
41
+ leave: {
42
+ animation: "",
43
+ start: "",
44
+ end: ""
45
+ }
46
+ }
47
+ }
21
48
  end
22
49
  end
@@ -3,9 +3,9 @@
3
3
  # Tailwind CSS v4
4
4
  module UltimateTurboModal::Flavors
5
5
  class Tailwind < UltimateTurboModal::Base
6
- DIV_DIALOG_CLASSES = "relative group z-50"
7
- DIV_OVERLAY_CLASSES = "fixed inset-0 bg-gray-900/70 transition-opacity dark:bg-gray-900/80"
8
- DIV_OUTER_CLASSES = "fixed inset-0 overflow-y-auto sm:max-w-[80%] md:max-w-3xl sm:mx-auto m-4"
6
+ DIV_MODAL_CONTAINER_CLASSES = "relative group z-50"
7
+ DIV_OVERLAY_CLASSES = "fixed inset-0 bg-gray-900/70 transition-opacity dark:bg-gray-900/80 opacity-0"
8
+ DIV_DIALOG_CLASSES = "fixed inset-0 overflow-y-auto sm:max-w-[80%] md:max-w-3xl sm:mx-auto m-4 opacity-0"
9
9
  DIV_INNER_CLASSES = "flex min-h-full items-start justify-center pt-[10vh] sm:p-4"
10
10
  DIV_CONTENT_CLASSES = "relative transform max-h-screen overflow-hidden rounded-lg bg-white text-left shadow-lg transition-all sm:my-8 sm:max-w-3xl dark:bg-gray-800 dark:text-white"
11
11
  DIV_MAIN_CLASSES = "group-data-[padding=true]:p-4 group-data-[padding=true]:pt-2 overflow-y-auto max-h-[75vh]"
@@ -17,5 +17,32 @@ module UltimateTurboModal::Flavors
17
17
  BUTTON_CLOSE_SR_ONLY_CLASSES = "sr-only"
18
18
  CLOSE_BUTTON_TAG_CLASSES = "text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
19
19
  ICON_CLOSE_CLASSES = "w-5 h-5"
20
+
21
+ TRANSITIONS = {
22
+ overlay: {
23
+ enter: {
24
+ animation: "ease-out duration-300",
25
+ start: "opacity-0",
26
+ end: "opacity-100"
27
+ },
28
+ leave: {
29
+ animation: "ease-in duration-200",
30
+ start: "opacity-100",
31
+ end: "opacity-0"
32
+ }
33
+ },
34
+ dialog: {
35
+ enter: {
36
+ animation: "ease-out duration-300",
37
+ start: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
38
+ end: "opacity-100 translate-y-0 sm:scale-100"
39
+ },
40
+ leave: {
41
+ animation: "ease-in duration-200",
42
+ start: "opacity-100 translate-y-0 sm:scale-100",
43
+ end: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
44
+ }
45
+ }
46
+ }
20
47
  end
21
48
  end
@@ -3,9 +3,9 @@
3
3
  # Tailwind CSS v3
4
4
  module UltimateTurboModal::Flavors
5
5
  class Tailwind3 < UltimateTurboModal::Base
6
- DIV_DIALOG_CLASSES = "relative z-50"
6
+ DIV_MODAL_CONTAINER_CLASSES = "relative z-50"
7
7
  DIV_OVERLAY_CLASSES = "fixed inset-0 bg-gray-900 bg-opacity-70 transition-opacity dark:bg-gray-900 dark:bg-opacity-80"
8
- DIV_OUTER_CLASSES = "fixed inset-0 overflow-y-auto sm:max-w-[80%] md:max-w-3xl sm:mx-auto m-4"
8
+ DIV_DIALOG_CLASSES = "fixed inset-0 overflow-y-auto sm:max-w-[80%] md:max-w-3xl sm:mx-auto m-4"
9
9
  DIV_INNER_CLASSES = "flex min-h-full items-start justify-center pt-[10vh] sm:p-4"
10
10
  DIV_CONTENT_CLASSES = "relative transform max-h-screen overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:max-w-3xl dark:bg-gray-800 dark:text-white"
11
11
  DIV_MAIN_CLASSES = "p-4 pt-2 overflow-y-auto max-h-[75vh]"
@@ -17,5 +17,32 @@ module UltimateTurboModal::Flavors
17
17
  BUTTON_CLOSE_SR_ONLY_CLASSES = "sr-only"
18
18
  CLOSE_BUTTON_TAG_CLASSES = "text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
19
19
  ICON_CLOSE_CLASSES = "w-5 h-5"
20
+
21
+ TRANSITIONS = {
22
+ overlay: {
23
+ enter: {
24
+ animation: "ease-out duration-300",
25
+ start: "opacity-0",
26
+ end: "opacity-100"
27
+ },
28
+ leave: {
29
+ animation: "ease-in duration-200",
30
+ start: "opacity-100",
31
+ end: "opacity-0"
32
+ }
33
+ },
34
+ dialog: {
35
+ enter: {
36
+ animation: "ease-out duration-300",
37
+ start: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
38
+ end: "opacity-100 translate-y-0 sm:scale-100"
39
+ },
40
+ leave: {
41
+ animation: "ease-in duration-200",
42
+ start: "opacity-100 translate-y-0 sm:scale-100",
43
+ end: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
44
+ }
45
+ }
46
+ }
20
47
  end
21
48
  end
@@ -3,9 +3,10 @@
3
3
  # Vanilla CSS
4
4
  module UltimateTurboModal::Flavors
5
5
  class Vanilla < UltimateTurboModal::Base
6
- DIV_DIALOG_CLASSES = "modal-container"
7
- DIV_OVERLAY_CLASSES = "modal-overlay"
8
- DIV_OUTER_CLASSES = "modal-outer"
6
+ DIV_MODAL_CONTAINER_CLASSES = "modal-container"
7
+ # Include enter-start classes so initial paint is hidden and transitions can animate smoothly
8
+ DIV_OVERLAY_CLASSES = "modal-overlay modal-transition-overlay-enter-start"
9
+ DIV_DIALOG_CLASSES = "modal-outer modal-transition-dialog-enter-start"
9
10
  DIV_INNER_CLASSES = "modal-inner"
10
11
  DIV_CONTENT_CLASSES = "modal-content"
11
12
  DIV_MAIN_CLASSES = "modal-main"
@@ -17,5 +18,32 @@ module UltimateTurboModal::Flavors
17
18
  BUTTON_CLOSE_SR_ONLY_CLASSES = "sr-only"
18
19
  CLOSE_BUTTON_TAG_CLASSES = "modal-close-button"
19
20
  ICON_CLOSE_CLASSES = "modal-close-icon"
21
+
22
+ TRANSITIONS = {
23
+ overlay: {
24
+ enter: {
25
+ animation: "modal-transition-overlay-enter-animation",
26
+ start: "modal-transition-overlay-enter-start",
27
+ end: "modal-transition-overlay-enter-end"
28
+ },
29
+ leave: {
30
+ animation: "modal-transition-overlay-leave-animation",
31
+ start: "modal-transition-overlay-leave-start",
32
+ end: "modal-transition-overlay-leave-end"
33
+ }
34
+ },
35
+ dialog: {
36
+ enter: {
37
+ animation: "modal-transition-dialog-enter-animation",
38
+ start: "modal-transition-dialog-enter-start",
39
+ end: "modal-transition-dialog-enter-end"
40
+ },
41
+ leave: {
42
+ animation: "modal-transition-dialog-leave-animation",
43
+ start: "modal-transition-dialog-leave-start",
44
+ end: "modal-transition-dialog-leave-end"
45
+ }
46
+ }
47
+ }
20
48
  end
21
49
  end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "json"
5
+ require "pathname"
6
+ require_relative "base"
7
+
8
+ module UltimateTurboModal
9
+ module Generators
10
+ class UpdateGenerator < UltimateTurboModal::Generators::Base
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ desc "Updates UltimateTurboModal: aligns npm package version to gem version and refreshes the configured flavor initializer."
14
+
15
+ def update_npm_package_version
16
+ package_json_path = rails_root_join("package.json")
17
+
18
+ unless File.exist?(package_json_path)
19
+ say "No package.json found. Skipping npm package version update.", :yellow
20
+ return
21
+ end
22
+
23
+ begin
24
+ json = JSON.parse(File.read(package_json_path))
25
+ rescue JSON::ParserError => e
26
+ say "Unable to parse package.json: #{e.message}", :red
27
+ return
28
+ end
29
+
30
+ package_name = "ultimate_turbo_modal"
31
+ new_version = UltimateTurboModal::VERSION.to_s
32
+
33
+ # Special case: demo app links to local JS package; never update its version
34
+ if json.dig("dependencies", package_name) == "link:../javascript" ||
35
+ json.dig("devDependencies", package_name) == "link:../javascript"
36
+ say "Detected local link for '#{package_name}' (link:../javascript). Skipping version update.", :blue
37
+ return
38
+ end
39
+
40
+ updated = false
41
+
42
+ %w[dependencies devDependencies].each do |section|
43
+ next unless json.key?(section) && json[section].is_a?(Hash)
44
+
45
+ if json[section].key?(package_name)
46
+ old = json[section][package_name]
47
+ json[section][package_name] = new_version
48
+ updated = true if old != new_version
49
+ end
50
+ end
51
+
52
+ if updated
53
+ File.write(package_json_path, JSON.pretty_generate(json) + "\n")
54
+ say "Updated #{package_name} version in package.json to #{new_version}.", :green
55
+ else
56
+ say "Did not find #{package_name} in package.json dependencies. Nothing to update.", :blue
57
+ end
58
+ end
59
+
60
+ def install_js_dependencies
61
+ install_all_js_dependencies
62
+ end
63
+
64
+ def copy_flavor_file
65
+ flavor = detect_flavor
66
+ unless flavor
67
+ say "Could not determine UTMR flavor. Skipping flavor file copy.", :yellow
68
+ return
69
+ end
70
+
71
+ template_rel = "flavors/#{flavor}.rb"
72
+ template_abs = File.join(self.class.source_root, template_rel)
73
+
74
+ unless File.exist?(template_abs)
75
+ say "Flavor template not found for '#{flavor}' at #{template_abs}.", :red
76
+ return
77
+ end
78
+
79
+ target_path = "config/initializers/ultimate_turbo_modal_#{flavor}.rb"
80
+ copy_file template_rel, target_path, force: true
81
+ say "Copied flavor initializer to #{target_path}.", :green
82
+ end
83
+
84
+ private
85
+
86
+ def detect_flavor
87
+ command = nil
88
+ if File.exist?(rails_root_join("bin", "rails"))
89
+ command = "#{rails_root_join("bin", "rails")} runner \"puts UltimateTurboModal.configuration.flavor\""
90
+ else
91
+ command = "bundle exec rails runner \"puts UltimateTurboModal.configuration.flavor\""
92
+ end
93
+
94
+ output = `#{command}`
95
+ flavor = output.to_s.strip
96
+ flavor.empty? ? nil : flavor
97
+ rescue StandardError => e
98
+ say "Error determining flavor via rails runner: #{e.message}", :red
99
+ nil
100
+ end
101
+
102
+ def rails_root_join(*args)
103
+ Pathname.new(destination_root).join(*args)
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+
@@ -55,7 +55,7 @@ class UltimateTurboModal::Base < Phlex::HTML
55
55
  modal(&block)
56
56
  end
57
57
  elsif turbo_stream?
58
- Turbo::StreamsHelper.turbo_stream_action_tag("update", target: "modal") do
58
+ turbo_stream_action_tag("update", target: "modal") do
59
59
  modal(&block)
60
60
  end
61
61
  else
@@ -104,6 +104,15 @@ class UltimateTurboModal::Base < Phlex::HTML
104
104
  @advance_url || request&.original_url
105
105
  end
106
106
 
107
+ # Wraps yielded content in a Turbo Frame if the current request originated from a Turbo Frame
108
+ def maybe_turbo_frame(frame_id, &block)
109
+ if turbo_frame?
110
+ turbo_frame_tag(frame_id, &block)
111
+ else
112
+ yield
113
+ end
114
+ end
115
+
107
116
  def respond_to_missing?(method, include_private = false)
108
117
  self.class.included_modules.any? { |mod| mod.instance_methods.include?(method) } || super
109
118
  end
@@ -131,7 +140,7 @@ class UltimateTurboModal::Base < Phlex::HTML
131
140
  def outer_divs(&block)
132
141
  div_dialog do
133
142
  div_overlay
134
- div_outer do
143
+ div_outer_dialog do
135
144
  div_inner(&block)
136
145
  end
137
146
  end
@@ -144,12 +153,6 @@ class UltimateTurboModal::Base < Phlex::HTML
144
153
  modal_advance_url_value: advance_url,
145
154
  modal_allowed_click_outside_selector_value: allowed_click_outside_selector,
146
155
  action: "turbo:submit-end->modal#submitEnd keyup@window->modal#closeWithKeyboard click@window->modal#outsideModalClicked click->modal#outsideModalClicked",
147
- transition_enter: "ease-out duration-100",
148
- transition_enter_start: "opacity-0",
149
- transition_enter_end: "opacity-100",
150
- transition_leave: "ease-in duration-50",
151
- transition_leave_start: "opacity-100",
152
- transition_leave_end: "opacity-0",
153
156
  padding: padding?.to_s,
154
157
  title: title?.to_s,
155
158
  header: header?.to_s,
@@ -163,7 +166,7 @@ class UltimateTurboModal::Base < Phlex::HTML
163
166
  end
164
167
 
165
168
  div(id: "modal-container",
166
- class: self.class::DIV_DIALOG_CLASSES,
169
+ class: self.class::DIV_MODAL_CONTAINER_CLASSES,
167
170
  role: "dialog",
168
171
  aria: {
169
172
  modal: true,
@@ -173,20 +176,38 @@ class UltimateTurboModal::Base < Phlex::HTML
173
176
  end
174
177
 
175
178
  def div_overlay
176
- div(id: "modal-overlay", class: self.class::DIV_OVERLAY_CLASSES)
179
+ div(id: "modal-overlay", class: self.class::DIV_OVERLAY_CLASSES, data: {
180
+ modal_target: "overlay",
181
+ transition_enter: self.class::TRANSITIONS[:overlay][:enter][:animation],
182
+ transition_enter_start: self.class::TRANSITIONS[:overlay][:enter][:start],
183
+ transition_enter_end: self.class::TRANSITIONS[:overlay][:enter][:end],
184
+ transition_leave: self.class::TRANSITIONS[:overlay][:leave][:animation],
185
+ transition_leave_start: self.class::TRANSITIONS[:overlay][:leave][:start],
186
+ transition_leave_end: self.class::TRANSITIONS[:overlay][:leave][:end]
187
+ })
177
188
  end
178
189
 
179
- def div_outer(&block)
180
- div(id: "modal-outer", class: self.class::DIV_OUTER_CLASSES, &block)
190
+ def div_outer_dialog(&block)
191
+ div(id: "modal-outer", class: self.class::DIV_DIALOG_CLASSES, data: {
192
+ modal_target: "outer",
193
+ transition_enter: self.class::TRANSITIONS[:dialog][:enter][:animation],
194
+ transition_enter_start: self.class::TRANSITIONS[:dialog][:enter][:start],
195
+ transition_enter_end: self.class::TRANSITIONS[:dialog][:enter][:end],
196
+ transition_leave: self.class::TRANSITIONS[:dialog][:leave][:animation],
197
+ transition_leave_start: self.class::TRANSITIONS[:dialog][:leave][:start],
198
+ transition_leave_end: self.class::TRANSITIONS[:dialog][:leave][:end]
199
+ }, &block)
181
200
  end
182
201
 
183
202
  def div_inner(&block)
184
- div(id: "modal-inner", class: self.class::DIV_INNER_CLASSES, data: content_div_data, &block)
203
+ maybe_turbo_frame("modal-inner") do
204
+ div(id: "modal-inner", class: self.class::DIV_INNER_CLASSES, data: content_div_data, &block)
205
+ end
185
206
  end
186
207
 
187
208
  def div_content(&block)
188
209
  data = (content_div_data || {}).merge({modal_target: "content"})
189
- div(id: "modal-content", class: self.class::DIV_CONTENT_CLASSES, data:, &block)
210
+ div(id: "modal-content", class: self.class::DIV_CONTENT_CLASSES, data: data, &block)
190
211
  end
191
212
 
192
213
  def div_main(&block)
@@ -5,7 +5,9 @@ require "phlex/deferred_render_with_main_content"
5
5
  require "ultimate_turbo_modal/configuration"
6
6
  require "ultimate_turbo_modal/railtie"
7
7
  require "ultimate_turbo_modal/base"
8
+ require "generators/ultimate_turbo_modal/base"
8
9
  require "generators/ultimate_turbo_modal/install_generator"
10
+ require "generators/ultimate_turbo_modal/update_generator"
9
11
 
10
12
  module UltimateTurboModal
11
13
  extend self
@@ -23,14 +23,28 @@ if [ "$1" != "--skip-gem" ]; then
23
23
  echo "Building and releasing gem..."
24
24
  bundle exec rake build
25
25
 
26
- # Check if Gemfile.lock is git dirty
27
- if ! git diff --quiet Gemfile.lock; then
28
- echo "Gemfile.lock is dirty. Adding, committing, and pushing."
29
- git add Gemfile.lock
30
- git commit -m "Update Gemfile.lock"
31
- bundle exec rake build
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" && yarn 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" && yarn install --force)
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/yarn.lock; then
42
+ echo "Lock files are dirty. Adding, committing, and pushing."
43
+ git add Gemfile.lock demo-app/Gemfile.lock demo-app/yarn.lock
44
+ git commit -m "Update lock files for demo app"
32
45
  fi
33
46
 
47
+ bundle exec rake build
34
48
  bundle exec rake release
35
49
  else
36
50
  echo "Skipping gem build and release..."
@@ -45,4 +59,3 @@ else
45
59
  fi
46
60
 
47
61
  echo "Done!"
48
-